Skip to content

Add SSL support to WebREPL #5611

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

Closed
wants to merge 0 commits into from
Closed

Conversation

Carglglz
Copy link
Contributor

@Carglglz Carglglz commented Feb 5, 2020

The two first commits are MicroPython related, the other three are a Python Websocket class that works both with WebREPL (ws) and WebREPL over SSL (wss).

Related to webrepl.py:

  • I use port 8833, but this can be changed to 8433 (or is there any 'official' port for this?)
  • Name convention for key and cert: 'SSL_certificate{}.der'.format(hexlify(unique_id()).decode())
    I use the unique id to be able to have unique key-cert for each device.
  • Name for WebREPL over SSL: I use 'WebSecureREPL' just to differentiate both since Websockets over SSL are called Websockets secure, but I guess this doesn't really matters

Related to websocket_helper.py:

  • Just minor changes to allow both tcp sockets and ssl sockets.

Related to ws_protocol.py, ws_client_handshake.py and websocket_client.py:
These are not part of the PR really, just for testing (although they could be included in this or another PR).

  • Both ws_protocol.py and ws_client_handshake.py are adaptations from https://github.com/danni/uwebsockets/tree/esp8266/uwebsockets, and there is no external dependency, just "pure" Python.
  • websocket_client.py is a simple class to sends commands and receive output. ( for both ws and wss). This could be adapted or included in some way in pyboard.py

Copy link
Contributor

@tve tve left a comment

Choose a reason for hiding this comment

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

Glad to see progress in securing MP! I'm a bit puzzled about how this is to be used, though... Needs docs, IMHO... E.g. how to generate keys, certs, auth, etc...

websocket_helper.server_handshake(cl)
if websslrepl:
if hasattr(uos, 'dupterm_notify'):
cl.setsockopt(socket.SOL_SOCKET, 20, uos.dupterm_notify)
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this dupterm_notify about? (Naive question)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My idea was to try to modify webrepl.py as little as possible to get SSL working and therefore increase the least amount of bytes so it does not have an impact in more constrained devices (esp8266). So yes since this was already commented I decided to not duplicate it.

cl = ssl.wrap_socket(cl, server_side=True, key=key, cert=cert)
websocket_helper.server_handshake(cl, ssl=True)
else:
websocket_helper.server_handshake(cl)
ws = uwebsocket.websocket(cl, True)
ws = _webrepl._webrepl(ws)
cl.setblocking(False)
# notify REPL on socket incoming data (ESP32/ESP8266-only)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I guess this comment answers my question above... It would have been nice to keep the ssl/non-ssl code together with the comment...

l = cl.readline()
# print(l)
while 1:
if not ssl:
Copy link
Contributor

Choose a reason for hiding this comment

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

Dumb question: why does the handshake have to be different for wss?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The main reason of a big percentage of changes is due to sockets and ssl sockets not having the same methods. SSL socket doesn't have a .makefile neither send or recv as a normal socket.



def set_ssl(flag):
with open('ssl_flag.py', 'wb') as sslconfig:
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason to create a separate config file just for the SSL flag? Maybe I'm misunderstanding...

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 is optional I think. Although is useful to change from ws to wss without modifying main.py

e.g. if you include this in main.py :

...
from ssl_flag import SSL
if SSL:
    webrepl.start(ssl=True)
else:
    webrepl.start()

So at the repl you can do at any time webrepl.set_ssl(True) or webrepl.set_ssl(False)
and after a soft/hard reset webrepl will start on ws or wss.

@Carglglz
Copy link
Contributor Author

Carglglz commented Feb 7, 2020

Hi @tve thanks for reviewing this. Take this PR as a "white canvas" since here are decisions that shouldn't be made by just one person.

Needs docs, IMHO... E.g. how to generate keys, certs, auth, etc...

Yes definitely, I already write something about that in here #5266, so as soon as https access is solved, I can put together the docs before merging. (Also there is #5543, so I have to do tests with latest releases too..)

tannewt added a commit to tannewt/circuitpython that referenced this pull request Nov 24, 2021
Don't use reset reason to skip writing boot_out.txt
@beyonlo
Copy link

beyonlo commented Jan 13, 2022

Hi all.

This PR has more than two years. Sorry, but I would like to know why it was not merged yet to mainstream?

Add SSL (secure websocket) is a great feature.

Thank you so much.

@Carglglz
Copy link
Contributor Author

@beyonlo head to upydev and check upyutils, there you can find these scripts plus some examples using ssl sockets 👍🏼

@beyonlo
Copy link

beyonlo commented Jan 17, 2022

Hi @Carglglz

@beyonlo head to upydev and check upyutils, there you can find these scripts plus some examples using ssl sockets 👍🏼

That's great. I will test those examples.

Anyway, now I'm curious, why after so long time, this PR still not in the mainstream? Maybe are there some conflict with this PR and some else?

Actually I and everyone that use the binary release are using the Web REPL without secure. Well, I will learn how to create my own binary Micropython firmware and try to add add this PR.

Thank you so much.

@Carglglz
Copy link
Contributor Author

Actually I and everyone that use the binary release are using the Web REPL without secure. Well, I will learn how to create my > own binary Micropython firmware and try to add add this PR.

@beyonlo you don't need to create your own firmware, check wss_repl.py and wss_helper.py in upyutils. Just upload those scripts and replace webrepl by wss_repl, and webrepl.start() by wss_repl.start(ssl=True). Also note this

Related to webrepl.py:

I use port 8833, but this can be changed to 8433 (or is there any 'official' port for this?)
Name convention for key and cert: 'SSL_certificate{}.der'.format(hexlify(unique_id()).decode())
I use the unique id to be able to have unique key-cert for each device.

So my version requires that a key and a certificate to be present in the device following that naming pattern (SSL_certificate[UNIQUE_ID].der, SSL_key[UNIQUE_ID].der) in DER format. (This enables device authentication).

Anyway, now I'm curious, why after so long time, this PR still not in the mainstream? Maybe are there some conflict with this >PR and some else?

You can check for context my initial issue #5543, lack of support of WebREPL website #5266, and the awesome ongoing work of @tve at #5840 and other folks, e.g. #7315.
There is also a long tail of issues, and PR related work. So yes, there is interest in support ssl + asyncio / websockets in MicroPython. I don't know when all that work will be merged but it is definitely in the pipeline 🔧 ⚙️

@dpgeorge
Copy link
Member

We definitely want to have a way of doing a secure network connection to a remote device. And this is a good starting point for that.

But there are things to resolve first, before merging this. Eg how to provision a device with cert/keys. Also we want to move to SSLContext.

So let's keep this open.

@Carglglz
Copy link
Contributor Author

We definitely want to have a way of doing a secure network connection to a remote device. And this is a good starting point for that.

I agree

But there are things to resolve first, before merging this. Eg how to provision a device with cert/keys. Also we want to move to SSLContext.

About that, I've put together a USER_C_MODULE mpy-mbedtls with mbedtls and x509 modules that allow to:

  • Generate ECDSA key pair

  • Derive public key from private key

  • Sign data

  • Verify signature

  • Generate a certificate signing request (CSR)

  • Parse certificate

  • Verify certificate

I didn't make a PR/Draft since I don't know where this should be included or if this should be included by default.

Also for future work about provisioning a device with certs signed by a CA, I've looked into implement a protocol ie ACME to automate cert renewal, but I'm not sure exactly since it seems to be server oriented only (ie HTTPS).

@tve
Copy link
Contributor

tve commented Nov 15, 2022

But there are things to resolve first, before merging this. Eg how to provision a device with cert/keys. Also we want to move to SSLContext.

I'm a big fan of HTTPS but the moment you touch web browsers it's a total PITA for embedded devices. I would highly recommend testing various set-ups using an rPi or other linux box before hacking mpy!

I recently worked on an embedded rPi project and reached the conclusion that the only viable approach is http://local-ip.co (which is a slightly simplified version of https://words.filippo.io/how-plex-is-doing-https-for-all-its-users/). The reason for all this is that all browsers restrict the bypassing of HTTPS checks more and more. It used to be just a warning, now you have to click through multiple warnings, except that some browsers (on windows in particular) no longer offer a bypass. There are a couple of good stackoverflow Q&A. Of course, if you assume everyone who will use the webrepl is a hacker and can find a browser that still works or uses a non-browser client then any solution will be fine...

@Carglglz
Copy link
Contributor Author

@tve

I'm a big fan of HTTPS but the moment you touch web browsers it's a total PITA for embedded devices. I would highly recommend testing various set-ups using an rPi or other linux box before hacking mpy!

You may want to have a look at microdot efforts to make it work, and I think right now both HTTPS and WSS work fine. So there should no problem using browser + WebREPL once this

how to provision a device with cert/keys

is figured out.

@tve
Copy link
Contributor

tve commented Nov 16, 2022

I've had TLS + MQTT + uasyncio + REPL working for several years on esp32, I have a good number of devices in "production" with great results. I'm not doing any non-TLS comm anymore. I like to use MQTT 'cause this way there is no issue about certs on the device, the certs are only on the server and I have the infrastructure to maintain LE certs there.

The experience I've had is that I used self-signed certs in an rPi project. It worked great for me on linux and android. But as soon as I handed the code out to "normal people" to test all hell broke loose. Some had trouble with all the hoops you have to jump through, others has systems/browsers that simply blocked the self-signed certs without offering any hoops to jump through. I then switched to the local-ip method and that solved pretty much all problems for me. YMMV...

@beyonlo
Copy link

beyonlo commented Nov 17, 2022

I'm using ESP32-S3 using MicroPython 1.19.1 with latest Microdot with SSL/TLS, in both: for the HTTPS Server and the Secure WebSocket Server (wss) and it is working perfectly with self-signed certs, with many users using that from the browsers. As it is self-signed, they (users) already know that need just to accept the Exception ("Accept Risk and continue") on the browser to open the HTML page hosted on the Microdot. However as explained here there is the https://github.com/FiloSottile/mkcert that make locally trusted certificates to be used. I need to get a time to test that, so users do not need anymore accept that Exception for the self-signed certs.

In my opinion to have the possibility to run the HTTPS Server and the Secure WebSocket Server (wss) on the Micropython is really amazing. So yes, +1 to have SSL support to WebREPL.

@luke-jr
Copy link

luke-jr commented Jan 20, 2023

Blindly trusting self-signed certificates, exceptions, local-ip.co, etc are all completely insecure and defeat the entire purpose of TLS.

Does anyone have a real solution? :/

@tve
Copy link
Contributor

tve commented Jan 21, 2023

"completely insecure" is not accurate, you're just lumping everything into one bucket.

Using something like local-ip.co with PFC cipher suites gives you a lot of security at the encryption level. What it doesn't give you is authentication of the server, e.g., someone else could take over the IP address and claim to be the server you want to connect to. There are other ways to ensure that the server you ended up connecting to is the one you wanted. In any case, it raises the barrier a whole lot over non-encrypted connections. If you want to go all the way, it's not that difficult to set-up DNS and the DNS-based challenges for Let's encrypt.

@luke-jr
Copy link

luke-jr commented Jan 21, 2023

Encrypting to an unknown and unverifiable key doesn't accomplish anything. Cleartext is fine if you have no risk of MITM, and if there's a MITM, they can just sit in between both encrypted machines with unauthenticated TLS. (Perhaps there's a benefit in the case of a merely passive MITM, but that's not typically a real scenario.)

DNS and Let's Encrypt doesn't provide a solution for local networks AFAIK?

@tve
Copy link
Contributor

tve commented Jan 21, 2023

Encrypting to an unknown and unverifiable key doesn't accomplish anything.

You're not doing that if you're using ciphers with PFC.

@Carglglz
Copy link
Contributor Author

@luke-jr can you explain what is your use case?
e.g. I'm using mTLS in my local network, which means I'm my own "Certification Authority", so

  • I generate an encrypted CA key and a CA certificate.
  • Then I generate an encrypted host key and a CSR that I sign with the CA key to generate a host certificate.
  • Now for a device I use mpy-mbedtls to generate the key in the device (so the key never leaves the device) and a CSR that is sent to the CA which creates a certificate for the device and sent this certificate with the CA certificate back to the device.

So now if I want to connect to a device, both parts needs to present a valid certificate signed by the CA i.e. mTLS
In theory this should be enough to avoid MITM, or there is something that I'm missing?

If you want to allow anyone to connect to your device in your local network then you need to provision
them with your own CA certificate (maybe using a QR or NFC tag?) or use this

If you want to go all the way, it's not that difficult to set-up DNS and the DNS-based challenges for Let's encrypt.

Although I haven't tested the latter but I guess it should be possible...

PD: @dpgeorge I closed this by mistake while updating my master branch...
anyway I could redo this PR if it were necessary 👍🏼

@luke-jr
Copy link

luke-jr commented Jan 22, 2023

@Carglglz Yes, creating your own CA works. My objection is only to suggestions that don't have a way to authenticate. My use case is to ensure someone cannot replace my devices with malicious ones or intercept the passwords for them, even if they physically MITM their network connections.

@Carglglz
Copy link
Contributor Author

My use case is to ensure someone cannot replace my devices with malicious ones or intercept the passwords for them, even if they physically MITM their network connections.

@luke-jr passwords are sent after the TLS handshake is done, but if the devices are physically accessible, then you probably should use flash encryption and secure boot (although AFAIK this is platform dependent and not part of MicroPython), otherwise someone could replace/copy the contents which will be as dangerous as no encryption at all...

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.

5 participants