jimmywarting / StreamSaver.js

StreamSaver writes stream to the filesystem directly asynchronous
https://jimmywarting.github.io/StreamSaver.js/example.html
MIT License
4.04k stars 418 forks source link

Preventing eavesdropping #99

Closed borisreitman closed 5 years ago

borisreitman commented 5 years ago

How can I make sure that what I write into the stream in the main page can't be observed by any other component ? By the iframe? By the service worker? I understand that anyone can use tee() on a readable stream to observe what the main page is writing into it? Can you actually explain what is going on -- what is writing to whom, so that I can evaluate the security model of this.

jimmywarting commented 5 years ago

As long as you are using our "man in the middle" + service worker, it would be possible for only me (and github themself) to change the service worker or the iframe to include some analytics or eavesdropping on the stream. But I'm never going to do that. if you want to avoid this you have to put up the service worker on your own domain (but requires to operate on secure contexts - https)

1) StreamSavers creates a MessageChannel (port1 & port2) 2) port2 is sent to iframe 3) iframe forwards the port2 to the service service worker

At this point all postMessages have been sent in one direction (main -> iframe -> service worker) so there is no window.onmessage that is going on that would make (say facebooks javascript SDK) to listen on anything that is sent back from the service worker to the main thread. You now have a bidirectional channel between the write stream and the output stream. This is made so that someone else don't accidentally write to the same output stream and makes the links a one time only link. (trying to open the link again will result in a failure (404))

So the iframe could have created a "tee" like thing of all message ports. But nothing else have the possibility to alter the mitm since there is no external dependencies and just vanilla javascript

At that point the iframe (mitm) is pretty meaningless and dose not eavesdrop one any packages. it can now be closed/destroyed. But on secure websites the mitm is kept alive to ping the service worker every now and then to keep the service worker alive for longer. When using a insecure site a popup is open in order to install and forward the MessageChannel port to the service worker and then immediately closed. So you can be sure that the mitm iframe is not eavesdropping on anything. But now you only have few minutes or seconds to save everything. before the service worker goes idle

4) Service worker now generates a uniq link and send that back to the MessagePort (WriteStream) that tells the you which link should be opened to start the download

This download link is a cross origin so no Ajax can be made to read the data. For this you would need Foreign Fetch but that have been removed. So even if someone somehow manage to get the download link by monkey patching or proxy the onmessage event it would all just be pointless in trying to read the data, only thing you can do is open up the link. The link is not even sharable to other browsers since the link is just generated with service worker and your browser

5) You now send a lot of packages directly to the service worker that will output the data to the associated ReadableStream that was generated for the one time unik link.

so to cap it up

hmm, that was a lot of explanation. anyhow that just one of two mode. the second one will be easier

Transferable ReadableStream is on the horizon. so instead of writing and listening to packages on MessageChannel. you can just write directly to the output stream. so that neither of the mitm or the service worker has to be alive or pinged or being able to eavesdrop. ReadableStreams can still be tee'ed but it's not done anywhere.

The data mostly just flow one direction

WriteStream -> mitm -> service worker -> readableStream after the initial message it's WriteStream -> service worker -> readableStream

with transferable stream it looks like WriteStream -> mitm -> service worker -> readableStream after the initial message WriteStream -> readableStream

Still doe service worker must still reply back that you are are able to open up the link and that is the only communication service worker sends back and that is made to the port that you first created. service worker -> WriteStream

I can't think of any way a third party domain would be able to eavesdrop on my mitm or service worker that is hosted on github pages. I'm not even able to listen or figuring out what the download link is going to be. even if i where i could not read the data

borisreitman commented 5 years ago

Hi Jimmy, that was a very thorough explanation. I'm mostly interested in a secure context scenario. Why is the iframe needed at all in this case? Can the main page register a service worker?

For me the issue is that I don't trust the service worker, even if it's hosted on my own domain example.com. I only trust the my main HTML page. That's because the service worker is not verified by SRI. As I see it, even if readableStream is going to be transferable, the service worker could tee() it, and send it to another page hosted on example.com that may be open in another tab. In this case, even if my page deregisters the service worker, another tab could observe the data in the stream. So, is there a security work around?

jimmywarting commented 5 years ago

Can the main page register a service worker?

Yes, But StreamSaver dose not do that, i built it so that both secure and insecure context could both use Service Workers, hence why a mitm popup was needed. So i just used it as a iframe in secure context also too ease the pain of having to install a service worker. and not being conflicted with someone that already has a service worker on there own website. makes it easier to just include a import a script from npm or a cdn and fiddle with it directly

I don't trust the service worker, even if it's hosted on my own domain example.com. I only trust the my main HTML page

I would trust my Service Worker more then my own Main page (and that is talking outside of the StreamSaver scope) service worker has a fairly restricted Content Security Policy (CSP) built in that it can only register a script that is hosted on your own domain, and under particular paths (scopes) You can do more private things there that are outside of the DOM where all eval scrips mostly lies.

sure. it may not have a way to use SRI with serviceWorker.register, importScripts and import But at least you can use integrity with the fetch api

jimmywarting commented 5 years ago

Makes me curious what you are building that is using SRI so heavily throughout your hole site 🤓

borisreitman commented 5 years ago

I'm still in "stealth" mode.