FirefoxOS/New security model
- 1 Goals
- 2 New Security Model
- 3 Implementation
- 4 2.5 Sprint Status
- 5 Meeting Note
- 6 References
- Enable exposing "sensitive APIs" to 3rd party developers.
- Use the same update and security model for gaia and for 3rd party content.
- Don't require content which uses "senstivie APIs" to be installed. Users should be able to simply browse to it.
- Don't have separate cookie jars for separate apps. At least for normal content which doesn't use "sensitive APIs".
- Ensure that content which uses "sensitive APIs" always runs in a separate process. Enforce in the parent process that only these separate processes can trigger "sensitive APIs". I.e. hacking a child process should not permit access to more sensitive APIs.
- Enable content which uses "sensitive APIs" to have normal http(s) URLs such that they can use OAuth providers like facebook.
- Enable content which uses "sensitive APIs" to use service workers.
"Sensitive APIs" here means APIs that we have not figured out how to safely expose to normal web pages. About 5-10% of the content in our marketplace falls into this category, and none of the content on the rest of the web fall into this category. I.e. most content does not use sensitive APIs, and can and should remain as normal websites.
New Security Model
Signing - bug 1153420
We will require that all content which uses "sensitive APIs" is signed. For now only the firefox marketplace will be allowed to do the signing. Possibly this will be changed in the future, but that's likely more a policy change than a code change.
Signing is done by having the developer package the content into a package and submit it to the mozilla marketplace. The marketplace will review the app and then add a signature to the package. The developer can then download the signed package and upload to the developer's website.
♦ Issue: Should we allow other forms manual review of each app? Can the marketplace "review a developer" and give the developer access to automatic signing?
The format used for the packaging will be the one defined in the W3C packaging spec draft. A header is added to the package to indicate that it's a signed package. The advantage of this packaging format, compared to zip, is that it's streamable.
The format used for the signature is still to be determined, but hopefully we can use the same file formats and file names as used today. However it's important that the signatures also cover the header data for each resource, as well as the header data for the package itself.
♦ Issue: Decide on exact signature format. Should we require that the signature-files live at the start of the package. That way we'd always have the signature available before the file contents covered by the signature.
Verifying signatures - bug 1153422
To load a webpage in a signed package, the user navigates to a URL like "https://website.com/RSSReader2000/package.pak!//index.html". The part before the "!//" is the URL to the package itself. The part after the "!//" is the resource path inside the package.
So loading signed content does not require an installation to happen. Simply navigating to a URL like the above is enough.
When the user navigates to such a page, Gecko will download the package from the webserver. Gecko will then see in the header of the package that the package is signed.
Before serving any resources from the package to the rest of Gecko, the network layer will first wait for the signatures to be loaded from the package. It will also verify that the resource that is currently being loaded is covered by, and matches, the signature.
We should likely cache which resources in the package that we've checked the signature of, so that we don't have to recheck if a resource is loaded multiple times.
Another thing that needs to be done before any content is served by the network layer is to look in the manifest and populate the nsIPermissionManager database with any permissions enumerated in the manifest. After having checked that the manifest properly matches the signature of course.
CSP - bug 1153423
We need to make sure that it can't load scripts from outside of the signed package. And we need to make sure that it can't use inline scripts.
The plan is to use the CSP code to accomplish this. We can mainly leverage existing code which enables applying a default CSP policy to certain content. We'll use this to apply a default CSP to all signed content similarly to how we currently apply a default CSP to all privileged apps.
We'll also need to extend it to enable it to enforce loads to happen "from same package", rather than just "from same origin".
We also can't allow signed content to be opened in an <iframe>, other than by pages from the same signed package. This is partially to prevent signed content from getting clickjacked. However it's also because we want to always open signed content in a separate OS process, and currently gecko does not support out-of-process plain <iframe>s.
Hopefully this is a restriction we can eventually relax, for example by allowing pages in a signed package to opt in to being iframe-able. But this will require out-of-process <iframe>s and so will have to wait.
Process isolation - bug 1153428
In order to ensure that only signed content can access the APIs that it has been signed for, we want to always use separate child processes to run such content.
This means that when a user navigates from an unsigned page to a signed page, that we need to switch which process render the pages. Right now this can only be done by creating a new <iframe mozbrowser>.
However only Gecko knows that a particular URL is signed. Gaia could not simply look at a URL to know if it will return signed content or not. And Gecko only knows that it's signed content once response data starts arriving.
Even if we add some way for gecko to signal to the <iframe mozbrowser> embedder that a new <iframe mozbrowser> needs to be created, this will make going "back"/"forward" between the two very messy.
We also need to change security checks that currently are done in the parent process. Currently many of them are heavily based on app-ids and installed apps. This may need to be changed.
Installing and updating - bug 1153432
Issue: How can we register web activities when we pin a page. And update those registries when the pinned manifest is updated.
Signed packages follow normal http semantics. I.e. if the package still exists in our http cache when the user revisits a signed page, but the cache headers indicate that the content needs to be updated, we do a normal GET request to see if a new version needs to be downloaded.
If a new version of the package is being sent, we follow the same behavior as when visiting a package for the first time. I.e. we need to reverify signatures as well as update any permissions in the nsIPermissionManager database.
However, we want to avoid having to download a whole package if just part of it has changed. In order to support that we hope to enable the server to respond to the GET request for an updated package with just a "diff" of what's changed between the previous and current version.
One possible way to do this would be to have gecko indicate that it supports a new type of content encoding as well as send the etag of the current package file. The server can then look at the etag and if it has (or can generate) a diff between the clients version and the latest version, it can respond with a special content-encoding as well as the package diff.
Gecko can then use the diff to patch the existing package.
Note that sending a diff is entirely the server's choice. If the server doesn't support this newly created diff mechanism, then it will simply serve a full package. Likewise if the user is on a very old version which the server doesn't have a diff for or if the diff has bigger size than the resulting package, the server can simply serve a full package.
In the case when a diff is received, it is probably fine to not support streaming package content. I.e. in that case its probably fine to wait for the full diff to be downloaded and applied, before returning any data from the Gecko network layer.
We do in that case still need to verify signatures of the new package version.
Installing a signed package mainly consists of pinning it in the http cache such that it doesn't get evicted. We still need to check for updates according to normal "app update" scheduling.
Service Workers - bug 1153433
One of the central pieces of the new Gaia architecture is the use of service workers. This isn't just to support offline for gaia apps, but also to support dynamic generation of page markup, and the ability to run logic in order to decide what resource to return for a given URL.
In order to make service workers work with the package update logic we should couple package update with service worker update. When the ServiceWorker spec require the browser to check for updates of, or download updates of, the ServiceWorker script, we instead update the full signed package.
This means that both when we do an "automatic" ServiceWorker update check, such as when the user visit a page which uses the ServiceWorker, and when the ServiceWorkerRegistration.update() function is called, that we update the full package rather than just the ServiceWorker script.
Once a new package has been downloaded, we go through the normal ServiceWorker update cycle. I.e. Gecko fire both "install" and "activate" events on the ServiceWorker. This will happen any time that a package is updated, even if the contents of the ServiceWorker script hasn't changed.
Gecko need to still serve the previous package content until the "activate" event for the new ServiceWorker version fires. I.e. until the new version has been installed, the old version of the package needs to be served for any network requests.
♦ Issue: How do we enable the newly installing serviceworker to load content from the new package version, even though the previous package version is the one pinned in the cache.
♦ Issue: Does CSP allow putting limits on where serviceworkers can be loaded from? We need to restrict ServiceWorkers scopes as well as script-urls to be from inside the package.
The biggest change here is that we should stop always using different cookie jars for different apps. In particular normal unsigned content should always use the same cookie jar no matter where it was loaded.
However signed packages will get their own cookies and IndexedDB data. Content inside a signed package will not share cookies, IndexedDB data, etc with unsigned content from the same domain. It will also not share data with content from other signed packages from the same domain. This is to ensure that unsigned content from the same domain can't read for example sensitive data that the signed content has cached in IndexedDB. And to prevent unsigned content from writing into the localStorage that signed content uses and thereby tricking the signed content into performing unintended actions.
However when pages from inside a signed package makes network requests to other websites, it should still use the normal cookies from those websites. And if a page from a signed package creates an <iframe> containing an unsigned website, then that website will be loaded with its normal cookies and will have access to its normal IndexedDB data.
In other words, each signed package acts like a separate website. They do not act like a separate "world"/"context".
The way that we will implement this is by generalizing the current appId and isInBrowserElement mechanism. We will introduce a OriginAttributes struct which will hold the "cookie jar" that is used for a given web page. We can then write policies for which parts of this struct is inherited into iframes, and which parts do not. The nsIPrincipal interface will contain one of these structs. We will also have functions for serializing this struct to a string, and for parsing such a string back into a struct.
Most code will treat this OriginAttributes struct as an opaque value. When we store data we store, as part of the key, the serialization of the OriginAttributes.
Two pages will only be considered same-origin if they have the same scheme+host+port, but also if all of the values inside the OriginAttributes of their nsIPrincipal have the exact same values.
We will then add a 'signedPkg' member to OriginAttributes. When a page is loaded from inside a signed package, we will read a package-identifier from inside the package and set it in the OriginAttributes of the nsIPrincipal of the page.
However, when we do the network request for the package itself, this is treated like other network requests to the webserver. I.e. the network request is considered as an unsigned request and so the normal cookies of the webserver is sent. We have to do it this way since when we fetch a signed package the first time, we don't know that the package is signed, and so we use the normal cookie jar for that domain.
The effect of this is that when we are making network requests, these are never affected by package signing. As mentioned above, requests for the package itself are not affected, and requests for pages inside the package don't send any cookies at all since they are simply loaded from the package.
However, when a page from a signed package access the document.cookies API, the cookies returned *do* use the full OriginAttributes. I.e. the document.cookies API is treated like other storage APIs like IndexedDB and localStorage.
♦ Issue: Verify with Honza that this is is doable and not complex. It should hopefully be the most natural way to write the cookie code.
If any page does a XHR request to a URL inside a signed package this is treated like any other network request and is permitted. This doesn't expose any user-private information and simply returns content that resides on the server.
Search for all the open nsec bugs: http://mzl.la/1SLAWum
- P1: Milestone 1 (Sept 4)
- P2: Milestone 2 (Oct 2)
- P3: Feature complete (Nov 2)
- P4: Post-2.5 release
Signing - bug 1153420
|1220025||P5||Tool to unpack streamable packages||NEW||No milestone|
1 Total; 1 Open (100%); 0 Resolved (0%); 0 Verified (0%);
Verifying signatures - bug 1153422
11 Total; 0 Open (0%); 11 Resolved (100%); 0 Verified (0%);
CSP - bug 1153423
|1178556||P2||Ensure script-src 'self' is restricted to content inside the signed package||RESOLVED||Ethan Tseng [:ethan]||WONTFIX||No milestone|
|1179060||P2||Apply a default CSP for signed packaged content||RESOLVED||Ethan Tseng [:ethan]||WONTFIX||No milestone|
|1179061||P2||Create CSP tests for signed packages||RESOLVED||Ethan Tseng [:ethan]||WONTFIX||No milestone|
|1179062||--||investigate marketplace for apps that use CSP||RESOLVED||Christiane Ruetten [INACTIVE]||FIXED||No milestone|
|1179064||P2||Ensure that service worker code for signed packages is contained within the package||RESOLVED||Dimi Lee [:dimi]||WONTFIX||No milestone|
|1180637||--||Packaged Apps do not apply CSP||RESOLVED||Valentin Gosu [:valentin] (he/him) [vacation until Aug 1st]||FIXED||No milestone|
|1181137||P1||Packaged Apps do not apply security headers||RESOLVED||Henry Chang [:hchang]||FIXED||No milestone|
7 Total; 0 Open (0%); 7 Resolved (100%); 0 Verified (0%);
Process isolation - bug 1153428
11 Total; 1 Open (9.09%); 10 Resolved (90.91%); 0 Verified (0%);
Installing and updating - bug 1153432
16 Total; 1 Open (6.25%); 15 Resolved (93.75%); 0 Verified (0%);
Service Workers - bug 1153433
|1131322||--||Service Workers for Gaia||RESOLVED||WONTFIX||No milestone|
|1181389||P2||check for full package update when doing service worker update||RESOLVED||Dimi Lee [:dimi]||WONTFIX||No milestone|
|1181390||P2||fire install and activate events on service worker||RESOLVED||Dimi Lee [:dimi]||WONTFIX||No milestone|
|1181391||--||Preserve previous package until activate event||RESOLVED||Dimi Lee [:dimi]||INVALID||No milestone|
4 Total; 0 Open (0%); 4 Resolved (100%); 0 Verified (0%);
|1163254||P1||Add signedPkg OriginAttribute for new Firefox OS security model||RESOLVED||Henry Chang [:hchang]||FIXED||No milestone|
|1164292||--||Clean up various CAPS pieces and make nsExpandedPrincipal::origin do something useful||RESOLVED||Bobby Holley (:bholley)||FIXED||No milestone|
|1164977||--||Consolidate subsumes checks and attribute handling onto BasePrincipal||RESOLVED||Bobby Holley (:bholley)||FIXED||No milestone|
|1165162||--||Include all non-default app attributes in stringified nsIPrincipal::origin||RESOLVED||Bobby Holley (:bholley)||FIXED||No milestone|
|1165214||--||DOMStorageManager should use origin for ScopeKey and QuotaKey||RESOLVED||Honza Bambas (:mayhemer)||FIXED||No milestone|
|1165217||--||Use origin attribute in nsIUsageCallback||RESOLVED||Nika Layzell [:nika] (ni? for response)||FIXED||No milestone|
|1165219||--||Use origin in ManagerId||RESOLVED||Steven Englehardt [:englehardt]||DUPLICATE||No milestone|
|1165224||--||Use origin in QuotaManager||RESOLVED||DUPLICATE||No milestone|
|1165256||P2||use origin for app_cache||RESOLVED||Honza Bambas (:mayhemer)||FIXED||No milestone|
|1165263||--||Use origin for nsIPermissionManager||RESOLVED||Nika Layzell [:nika] (ni? for response)||FIXED||No milestone|
|1165267||P1||Use OriginAttributes for nsCookieService||RESOLVED||Ethan Tseng [:ethan]||FIXED||No milestone|
|1165269||P2||Use origin for http cache||RESOLVED||Honza Bambas (:mayhemer)||FIXED||No milestone|
|1165270||--||Use origin for BroadcastChannel||RESOLVED||Andrea Marchesini [:baku]||FIXED||No milestone|
|1165272||--||unify Get*CodebasePrincipal with createCodebasePrincipal in nsIScriptSecurityManager||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1165277||--||Use origin in SessionStorage.jsm||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1165466||P2||Fix up docshell and loadcontext inheriting code in nsIScriptSecurityManager to use originAttributes rather than explicitly querying appid/browser||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1165787||--||Use origin in RequestSyncService.jsm||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1167098||P2||IPC change for the new FirefoxOS security model||RESOLVED||WONTFIX||No milestone|
|1167100||--||User nsIPrincipal.originAttribute in ContentPrincipalInfo||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1168300||--||Add cookieJar attribute and notify clear-cookiejar-data||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1168777||P2||remove mozIApplicationClearPrivateDataParams||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||WONTFIX||No milestone|
|1172080||--||Using an optional '!' in a principal's origin to denote extra data is spoofable||RESOLVED||Nika Layzell [:nika] (ni? for response)||FIXED||No milestone|
|1179985||P2||[meta] Make all Origin-Related APIs OriginAttributes-aware||RESOLVED||FIXED||No milestone|
|1182347||--||Eliminate nsIPrincipal::cookieJar||RESOLVED||Bobby Holley (:bholley)||FIXED||No milestone|
|1188776||--||Remove appId/isInBrowserElement from BroadcastChannel||RESOLVED||Andrea Marchesini [:baku]||FIXED||No milestone|
|1188777||--||e10: use originAttributes from child to parent process.||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||INVALID||No milestone|
|1191653||--||Listen to clear-origin-data in nsPermissionManager.cpp||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1191740||--||Add OriginAttributes in TabContext||RESOLVED||Kan-Ru Chen [:kanru] (UTC+9)||FIXED||No milestone|
|1195930||P1||Use origin in QuotaManager||RESOLVED||Jan Varga [:janv]||FIXED||No milestone|
|1196644||P2||Add OriginAttributes to PermissionSettings||RESOLVED||Stephanie Ouillon [:arroway] (needinfo me)||WONTFIX||No milestone|
|1196665||--||Add originAttributes into SpecialPowers||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1197093||--||add originAttributes to nsIOfflineCacheUpdate||RESOLVED||Honza Bambas (:mayhemer)||DUPLICATE||No milestone|
|1201042||P1||Update HTTP cache index format to work with OriginAttributes' suffix||RESOLVED||Michal Novotny [:michal]||FIXED||No milestone|
|1209162||--||Create OriginAttributes subtypes for different uses and define conversion routines between them||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1209349||--||Audit the callers of the two-argument OriginAttributes constructor||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1210890||--||Create signedpackage inBrowser origin attribute to fix mozBrowser API in signed packages||RESOLVED||INVALID||No milestone|
|1210903||--||Modify cookie behavior for signed packages||RESOLVED||INVALID||No milestone|
|1212250||--||Use InheritFromDocToChildDocshell in TabContext when inheriting OriginAttributes from parent window||RESOLVED||DUPLICATE||No milestone|
|1213577||--||Use OriginAttributes in nsHttpAuthManager||RESOLVED||Honza Bambas (:mayhemer)||FIXED||No milestone|
|1214071||P1||Add APIs get/removeCookiesWithOriginAttributes() in nsICookieManager2.idl||RESOLVED||Jonathan Hao (inactive) [:jhao]||FIXED||No milestone|
|1225053||--||gfxSVGGlyphs.cpp should use correct origin attributes to create principal||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1225349||--||PrincipalOriginAttributes should inherit mSignedPkg accordingly by URI||RESOLVED||WONTFIX||No milestone|
|1225353||--||DocShell/NeckoOriginAttributes should inherit mSignedPkg accordingly by mSignedPkgInBrowser||RESOLVED||WONTFIX||No milestone|
|1227861||--||Add OriginAttributes getter/setter into nsIDocShell||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1230459||--||Use InheritFromDocToChildDocShell in LoadContext||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1233136||--||Audit all "clear-origin-data" consumers||RESOLVED||WONTFIX||No milestone|
|1237152||--||rename "clear-origin-data"||RESOLVED||Yoshi Cheng-Hao Huang [:allstars.chh][:allstarschh]||FIXED||No milestone|
|1267910||P1||Make the API add() and getCookiesFromHost() of the nsICookieManager2 OriginAttributes-aware||RESOLVED||Tim Huang[:timhuang]||FIXED||No milestone|
48 Total; 0 Open (0%); 48 Resolved (100%); 0 Verified (0%);