Open lightsofapollo opened 7 years ago
CC @pauljt
I'd like to try and clarify the problem we are trying to solve...
We have created a "web of things" gateway people can build themselves using a Raspberry Pi which enables you to monitor and control connected devices in your home over the web via a web application hosted on the gateway itself.
Secure remote access is achieved using HTTPS and a username and password configured on the gateway during first time setup. We provide a TLS tunnelling service which for convenience during first time setup allows users to easily set up a secure subdomain to access their gateway over the web. The Getting Started Guide shows how this works from the end user's point of view.
The service works by issuing a free subdomain, generating an SSL certificate via LetsEncrypt and setting up a secure tunnel from a Mozilla cloud server to the gateway using PageKite so that users don't have to manually open and forward a port on their router to the gateway. The initial setup of the tunnel happens over plain HTTP (via gateway.local over mDNS or a local IP), but after that point everything (including configuring the username and password) happens over HTTPS (e.g. https://foo.mozilla-iot.org). The user is free to configure DNS, TLS and NAT manually themselves if they want to use their own domain name or don't want to tunnel their traffic through a Mozilla server.
The problem with this is that when the user's Internet connection goes down, they can no longer access their secure subdomain even from within their local network, because HTTPS relies on DNS which relies on having an active Internet connection. We would like users to be able to continue to turn lights on and off from within their home when their Internet connection goes down!
Users could potentially still access their gateway using its local IP address (or gateway.local mDNS broadcast), but this would only be available using plain HTTP.
Some people would argue that plain HTTP inside the local network is actually fine and is how the vast majority of home routers work today, but many people think that with all the connected devices we're adding to our networks, we can no longer trust the devices on our own local network. An untrusted device on the local network could use a MITM attack to steal the password or JSON Web Token as it is exchanged over HTTP.
Manually switching between different web addresses depending on whether your Internet connection up is not very user friendly! Also, the web app hosted on the gateway is designed to be used in a standalone display mode without a URL bar. It's possible we could use Service Workers to automatically fail over to API calls in plain HTTP if the HTTPS connection is unavailable and client is connected to the same local network as the gateway, but that could be tricky to detect.
We experimented with DNS caching to allow continued access to the gateway over HTTPS during a temporary Internet outage, but we concluded that DNS caches in operating systems, browsers and routers are just too inconsistent and unpredictable to make this a reliable solution.
One proposed solution involves using WebRTC to create a secure peer to peer connection between the browser and the gateway which doesn't require HTTPS in order to continue working.
The purpose of our project is to "give things URLs on the web" and using a P2P WebRTC connection wouldn't really be achieving this as such, it works around it. Also WebRTC can be a very complex technology to set up and get working and in some cases requires a centralised server to initiate the connection.
Another proposed solution is for the client and the gateway to exchange keys of some kind while the HTTPS connection is available, so that they can continue to communicate securely over plain HTTP and be confident of each others identity when the HTTPS connection is unavailable. I suspect this is similar to what HomeKit does (Apple Developer registration required to view), by layering a custom security mechanism on top of plain HTTP.
The downside of this is that it requires custom encryption of the JSON payloads of REST/WebSocket API calls which may harm interoperability if everyone on the Web of Things used their own encryption scheme.
I think that's most of the solutions we have explored so far.
To recap, the requirements are:
Note: This last point means that the "explicit trust" approach described above where authentication happens using a Mozilla cloud service is far from ideal. The core principle of Project Things is to create a decentralised IoT using lessons learned from the web, which doesn't rely on a central point of control.
It may be that we come up with two solutions:
Some other solutions may become possible if the gateway is also the home router and provides the DNS configuration for the home network. It would be useful to discuss these solutions because it may be that at some point this becomes true, although for now the gateway is a Raspberry Pi separate to the home router.
On the W3C Web of Things mailing list I was pointed towards a W3C community group which was started this year to look into this issue.
They are currently at the stage of collecting use cases but there's at least one early stage idea of a solution.
There was also apparently breakout session at TPAC on this topic and just this week there was a workshop at the IETF about the related topic of distributed DNS.
We're not the only ones looking for a solution to this problem.
See also, this thread of the W3C WoT Working Group mailing list where I brought up this issue and there's been quite a lot of discussion https://lists.w3.org/Archives/Public/public-wot-wg/2017Jul/0018.html
I've been looking at the ServiceWorker angle and it looks like just substituting HTTP for HTTPS will not work. Everything I've tried has been blocked as mixed active content or by a NetworkError. I think it might be possible to rig up something disgusting where requests to images (or other passive content) are reinterpreted as API requests.
If you want to try investigating this further, the code I've been using to experiment is available at https://github.com/hobinjk/http-https-sw-test
If we perform a one-time redirect to the local http, it might be possible to use Forge to do some kind of https over http with the Let's Encrypt cert
Okay, I'm unable to stop thinking about this so I came up with a potentially interesting solution that falls under Plain HTTP with Encryption while remaining vaguely webby. The Gateway installs a ServiceWorker on https://gateway.mozilla-iot.org that when offline pops up a url to data:text/html,<entire source of offline-mode gateway>
(probably compressed and encoded, but morally equivalent). As far as I can tell data:
can make requests to CORS-enabled servers which means that the server and the offline-mode gateway can establish a tunnel using a pre-shared key. The offlline-mode page can even be seeded with the local IP address of the gateway.
tl;dr: a Service Worker can redirect the client to a previously generated data:
URL which can then make requests to local servers (e.g. https over ws).
This has the following drawbacks:
I'm pretty sure that the data:
url can freely use the SW cache of https://gateway.mozilla-iot.org to decrease the amount of content that needs to be inlined. If not, WebPack comes with a ton of facilities for base64-inlining everything
@hobinjk That's ingenious :P But also a huge hack.
It may be that we have to resort to a hack along these lines as a short term fix, but I don't really like the sound of turning the whole app into a huge data URL (and don't get me wrong, I've abused data URLs plenty in the past)! I think I would personally prefer to just redirect the whole web app to a plain HTTP .local domain than have something that looks so dubious show up in the URL bar - at least it's more obvious to the user what's going on that way.
A medium term solution might be us becoming the router, or at least the DHCP server for the network so we can provide a DNS configuration.
A long term solution may involve evolving DNS/TLS/HTTP themselves through the IETF which is something people in the W3C WG were talking about today.
@benfrancis Yeah it's definitely not something I actually want to do, I just had to share :)
I think directing to HTTP is the best current solution especially since with the current state of IoT security if someone's on your network they can probably mess with your devices anyway.
Potentially interesting work from the wot-sec minutes https://blog.filippo.io/how-plex-is-doing-https-for-all-its-users/
It turns out there may be a simple solution to this problem.
@martinthomson has suggested the use of the Alt-Svc
HTTP header (RFC 7838) which could be used to tell the browser to load resources from https://gateway.local rather than https://foo.mozilla-iot.org when it's available.
As long as gateway.local is returning a valid certificate for foo.mozilla-iot.org this will be invisible to the user and shouldn't trigger any security exceptions.
The browser should cache this response so that if the Internet connection goes down, any future requests should be routed to gateway.local.
When outside the local network, requests to gateway.local will fail and fall back to foo.mozilla-iot.org.
The only caveat I've thought of so far is that if you are connected to someone else's local network who also has a gateway.local, you may get routed to their gateway instead, at which point you may see a security exception rather than falling back to the remote origin (we need to test this). If this is a problem then we may want to encourage people to pick their own local hostname during first time setup to reduce collisions.
This does not solve the problem of HTTPS on local networks for devices which don't have a fully qualified domain name on the public Internet, for which Martin has a separate proposal.
The current implementation of alt-svc calls out to SpdyInformation which only lists h2 as a valid protocol for alt-svc. In Chrome an alt-svc of http/1.1 also has no effect. Based on a probably related file from chromium it looks like only quic and h2 are supported.
Another approach that I tried is lying about supporting h2 and hoping the browser does a downgrade to http/1.1. This didn't work in either Firefox or Chrome which I believe is by design.
@martinthomson It sounds like this might not work. Do you know why this is only supported in browsers for HTTP/2? I'm sure you showed me HTTP 1.1 examples.
@mcmanus might be able to help regarding the Alt-Svc thing. I didn't realize that this might be limited to h2.
While this is an old discussion, I want to share my findings (and proposals) with you. They might include valuable aspects of the problem: I've documented them in this blog-post here: https://dev.to/danielkun/where-is-https-for-iot-49ao
Also, there seems to be a meeting on this topic at the W3C TPAC 2018 (https://www.w3.org/2018/10/TPAC/schedule.html#cgs-thu) - I'm very eager to hear the results.
A good proposal has been made, which I assume will be discussed: https://github.com/dajiaji/proposals/blob/6bf4825383cf54d6be584c5e206f4631c60d87c7/draft_proposal_supporting_local_https_communication.md
What about adding a new Delegated-by
field in the certificate ?
The idea would be to have something like:
Delegated-By: my-dev.mozilla-iot.org, signature=0FABEDCEFFEDCFEDDFCEEF
where the signature is generated by mozilla-iot and would sign the current (self-signed) certificate.
The IoT will present self signed certificate that's signed by another public certificate, and the browser would have to download & assert the delegated certificate. This is like if mozilla-iot would be a CA by itself, but because it's not, the browser will display a kind warning (not the huge one you see actually when using self signed certificate) the first time. Then the self signed cert should be pinned and no warning shown afterward.
This protects against MITM hopefully since a MITM can not generate a valid certificate for the same "local" name, and even if he does, it would not match the pinned certificate that's stored in the browser.
This looks interesting (requires running a local DNS server, which we might be able to do on a router): https://github.com/arcturus/crazydnsle
That might not work well with plans to deploy DoH
Indeed, it'll not work for IoT because you need a Let's Encrypt certificate for your "mapped" name, and the IoT might:
When the internet is down we want to have a mechanism where we can still communicate with our devices (provided we are on the same network as those devices). To facilitate this we need some sort of "trust" mechanism where we use a trusted third party to exchange public keys.
We need to initially support two modes initially for this:
Implicit trust between gateway and client (they exchange public keys directly, potentially without protocol level security).
Explicit trust of a third party (i.e. cloud.mozilla-iot.org).
Requirements for implicit trust (required for this release):
[ ] Client is directly "logins in" with the gateway and will be given (in addition to their own public key) a public key for the gateway (which is used to verify messages from the gateway).
[ ] Client is given some mechanism (probably an ip to start) to discover gateway offline.
Requirements for explicit trust (may be moved into future release):
[ ] Restructure gateway so we can reuse (or perhaps just extract) login logic to a centralized cloud component (logins by the nature of this system must happen through this system in this configuration).
[ ] Gateways in "explicit" trust mode must register themselves with the third party entity (on first time setup). Gateways do not store the hashed password in this trust mode.
[ ] Logins in "explicit" trust mode must happen (i.e. password hashes are sent to) the trusted third party (mozilla-iot cloud or similar) and public keys are registered with this entity.
[ ] Client is given some mechanism (probably an ip to start) to discover gateway offline.