Re: [w3c/ServiceWorker] Preventing server-forced updates (#822)

While this doesn't solve the problem of *preventing* Service Worker updates, here's a rough sketch of an idea that might (with user co-operation) at least allow cobbling together a ToFU level of security to *detect* unauthorized updates, vet authorized updates, and fail-safe if an unvetted update occurs, on existing browsers:

- Call the actual "sensitive application data" (e.g. PGP private key) CS0.
- Each and every service worker is installed with a **unique per-installation** random secret, distributed as a "hard-coded constant" in the Service Worker's source code, wiped immediately from memory after generation (since at this time we're just aiming at ToFU security, *say* the server hasn't been compromised yet); call this CS1. This will fill a similar role to the [AEM machine validation secret](https://blog.invisiblethings.org/2011/09/07/anti-evil-maid.html#:~:text=if%20a%20user%20can%20see%20correct%20secret%20message%20(or%20perhaps%20a%20photo)%20being%20displayed%20on%20the%20screen%2C%20then%20it%20means%20that%20correct%20software%20must%20have%20booted), serving as living proof that the service worker is the same as it was when it was installed.
  * The endpoint that distributes the service workers must GENERALLY serve an HTTP 4XX response, except during active installation (which is indicated by an HTTPS-enabled cookie which is set by JS *immediately* before `register()` is called, and which is always unset by any non-4XX response from that endpoint)
- During "app setup" (Service Worker installation time), randomly generate a brief "challenge" passphrase and either generate or make the user generate a "client encryption" passphrase; call these P1 and PE.
- (Obviously, the service worker also does all the other things needed to keep a web application securely/immutably pinned.)
- Add Enc(CS1, CP) to LocalStorage. You need CS1 to reconstitute CP from LocalStorage.
- Add Enc(PE, Enc(CS1, CS0)) to LocalStorage.  You need PE *and* CS1 to reconstitute CS0 from LocalStorage.
  * For the love of god, please use at least half the available RAM for the KDF of the outer encryption layer here.
- Tell the user that they **MUST NOT EVER** disclose EP without verifying P1 first. Remind them that any request for PE, which is not accompanied by P1, is *certainly* from an adversary.
- Each time the application "starts up" (the page is visited), display P1 (maybe masked behind a clickwall with a reminder about shoulder-hoppers) and get EP input from the user; use that to decrypt CS0 into memory.
   * While the KDF is running, display a reminder to always validate P1 before entering EP
- When an application update is needed, the current version of the application follows this process:
   1. Fetch (using some API other than the service worker update URL) an alleged upcoming update of the service worker\*, which embeds pins for the alleged new version of the application
   2. Fetch and vet a hash of the alleged new version of the service worker\*. "Vetting" could include anything from checking it against a digital signature from the developer's offline signing keys, or multi-person geographically distributed [wrench](https://xkcd.com/538/)-resistant signatures, to checking multiple canaries, to getting the user to actually affirm its value.
      * \*Technically, this will just be a hash of "the service worker but with a dummy value for CS1".
   3. Once the new update's hash has been approved, execute this critical path subroutine:
      1. Assert that the *current* service worker has been installed with [`updateViaCache`](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/updateViaCache) to "all"
      2. Remind the user again that if they are prompted for EP again soon, that they **MUST NOT** disclose it unless P1 validates again
      3. Fetch a copy (from a **different** URL than the Service Worker's installation path) of the new service worker's source code
      4. Compare the hash of the new service worker's source code to the hash the user just approved
      5. If the hash compares unequal to the hash which the user had vetted, ABORT
      6. Modify the source code, replacing the embedded CS1 _\[some dummy value\]_ with the current value of CS1
      7. [Do an offline-update of the SW](https://stackoverflow.com/q/77233289/1874170), inserting the "modified" service worker that comprises validated source code plus device-specific value of CS1
      8. Restart the application (e.g. reload the page)

This should maybe be safe, since while an adversary can *overwrite* the existing service worker by simply serving a 200 OK response that would blow away CS (_cf._ an Evil Maid failing by blowing away TPM-protected secrets when factory-resetting the PC), I don't think there's any way for them to actually get at the source code of the *currently running* service worker.

The update mechanism is hella janky, though, and I'm less sure of it (especially since the "offline update" of a service worker isn't proven yet). The initial installation sort of *has* to include a leap of faith (that's ToFU), but I had hoped to allow the application to reduce the *surface area* the user has to think about to nothing more than matching an on-screen hash to an otherwise-known-good value. (Heck, you could even replace "user vetting" with )

-----

Obviously, this is a LOT of engineering work, and an additional UX burden of some song-and-dance. But, of course, at least *some* UX burden is absolutely necessary due to [this crux](https://github.com/w3c/ServiceWorker/issues/1680#issuecomment-1747606040). Certainly something more "batteries-included" for web applications to keep sensitive data safe from a surely upcoming adversarial takeover at an unknown future date would be nice.

-- 
Reply to this email directly or view it on GitHub:
https://github.com/w3c/ServiceWorker/issues/822#issuecomment-1747781786
You are receiving this because you are subscribed to this thread.

Message ID: <w3c/ServiceWorker/issues/822/1747781786@github.com>

Received on Wednesday, 4 October 2023 23:24:36 UTC