Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Permanent browser cache using ServiceWorker

I am designing a JavaScript secure loader. The loader is inlined in the index.html. The goal of the secure loader is to only load JavaScript resources are trusted. The contents of index.html are mostly limited to the secure loader. For security purposes, I want index.html (as stored in cache) to never change, even if my website is hacked.

How can I cache index.html without the server being able to tamper with the cache? I am wondering if ServiceWorkers can help. Effectively, the index.html would register a service worker for fetching itself from an immutable cache (no network request is even made).

like image 973
Randomblue Avatar asked Jan 17 '17 14:01

Randomblue


People also ask

What is use of Serviceworker?

Service workers are specialized JavaScript assets that act as proxies between web browsers and web servers. They aim to improve reliability by providing offline access, as well as boost page performance.

How much can a service worker cache?

This means each browser vendor decides cache limits and invalidation strategy. Up to this point Apple has decided to limit the service worker cache storage limit to roughly 50MB. It is actually limited to 50MiB, which equates to about 52MB.

How do I create a dynamic cache in a service worker?

We ll put the request into the caches - caches. put with the response clone returned from the fetch promise. Return the response. If promise got rejected fetch will go to caches (pre-cache) to match the request URL request.

Does service worker work on HTTP?

Service workers will register and work just fine as long as you access the localhost domain -- without HTTPS.


2 Answers

in chrome you can use FileSystem API

http://www.noupe.com/design/html5-filesystem-api-create-files-store-locally-using-javascript-webkit.html this allows you to then save and read files from a sand-boxed file-system though the browser.

As for other support it's not been confirmed as an addition to the HTML5 specification set yet. so it's only available in chrome.

You could also use the IndexDB system this is supported in all modern browsers.

you can use both these services inside a Service Worker to manage the loading and manage of the content however i have to question why would you want to you prevent your self from ever updating your index.html

like image 52
Barkermn01 Avatar answered Oct 06 '22 01:10

Barkermn01


This design goal of "secure javascript loading"/TOFU is typically associated with javascript crypto and browser secrets (e.g. Cyph, Mega), so I'll include some relevant recommendations along the way.

You're in dangerous territory. There are many dragons.

Option 1: Implement feross/infinite-app-cache to permanently cache the app in the browser

Here's the least amount of code required to achieve a TOFU web app in all browsers.

index.html:

<html manifest="manifest.appcache">

manifest.app cache:

CACHE MANIFEST
/manifest.appcache
/index.html
/script-loader.js

And make sure you serve the manifest with a content type of "text/cache-manifest".

Note: This standard is deprecated, however it will take some years before browsers disable this feature.

Problem: If an attacker (who has already hacked your server) can trick your users into visiting a URL not in the app cache, they can serve arbitrary code to extract/use browser secrets etc.

Solution: User input cannot be intercepted by a malicious page on the same domain (unless you do something really silly), so encrypt any browser secrets using a user-supplied password and dchest/scrypt-async-js.

Another thing to consider is the cached contents of a page CAN be extracted by the malicious page simply by using AJAX - so you HAVE to use user input, not just a random token rendered to the page.

Option 2: Extend the above solution with HPKP Suicide and a Service Worker

Implement HPKP Suicide to permanently "bake" the app into the browser by bricking the user's connection to the server.

If (and only if) you have implemented HPKP Suicide, service workers are now safe to use because the enforced max-age of 24 hours has no effect if the browser can't re-download the service worker.

index.html or script-loader.js:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/service-worker.js').catch(function (error) {
      console.error(error);
    });
  });
}

service-worker.js:

var files = [ 
  '/',
  'index.html',
  'script-loader.js'
].map(file => new Request(file))

self.addEventListener('install', () => {  
  caches.open('cache')
    .then(cache => {
      files.map(file => {
        fetch(file)
          .then(response => cache.put(file, response))
          .catch(err => console.error(err))
      })
    })
    .catch(err => console.error(err))
})

self.addEventListener('fetch', (e) => {
  var url = e.request.url.split('#')[0]

  if (!files.filter(file => file.url === url).length) {
    return e.respondWith(new Response('Not Found', { status: 404 }))
  }

  return e.respondWith(
    caches.match(e.request).then(cachedResponse => {
      if (cachedResponse) return cachedResponse

      return Promise.all([
        caches.open('cache'),
        fetch(e.request.clone())
      ]).then((results) => {
        var cache = results[0]
        var response = results[1]

        cache.put(e.request, response.clone())

        return response
      })
    })
  )
})

Important note: Without HPKP Suicide, using a service worker is LESS secure than infinite app cache because the service worker has a max-age of 24 hours, even if your server's cache directives set a higher age. Using service workers will also completely disable app cache. It's a waste of time trying to achieve this with service workers if you haven't implemented HPKP Suicide.

Discussion

  • HPKP suicide has lost the guarantee of permanence in Chrome as they have recently implemented a max-max-age of 60 days (see issue #523654 for discussion)
  • I strongly recommend you use TweetNaCl.js for all browser crypto: it takes effort to use incorrectly and unless you are encrypting large files the performance difference with WebCrypto is insignificant
like image 21
Tristan Hoy Avatar answered Oct 05 '22 23:10

Tristan Hoy