hypothesis / browser-extension

The Hypothesis browser extensions.
BSD 2-Clause "Simplified" License
490 stars 128 forks source link

Production Firefox extension will not install from local .xpi file #316

Open jboeke opened 4 years ago

jboeke commented 4 years ago

I am receiving the following when trying to install my Firefox extension build.

image

Steps to Reproduce:

  1. Clone repo at commit ea8fa71015616587e9e0cb17fd0f552d05c9094f.

  2. On Win 10, using MINGW64 shell provided by git bash (with administrative permissions), use Chocolatey to install make tools via the command choco install make

  3. git checkout -b my-branch in order to make a new local branch.

  4. Delete all files in build folder.

  5. Commit changes locally on my-branch.

  6. Make with command make SETTINGS_FILE=settings/firefox-prod.json. Output appears to be successful:

$ make SETTINGS_FILE=settings/firefox-prod.json
node_modules/.bin/browserify -t babelify -d src/background/index.js > build/extension.bundle.js.tmp
cat build/extension.bundle.js.tmp | node_modules/.bin/exorcist build/extension.bundle.js.map >build/extension.bundle.js
node_modules/.bin/mustache build/.settings.json src/manifest.json.mustache > build/manifest.json
cp -R node_modules/hypothesis/build/* build/client/build
rm build/client/build/manifest.json
tools/template-context-app.js build/.settings.json | node_modules/.bin/mustache - src/sidebar-app.html.mustache >build/client/app.html
tools/template-context-settings.js build/.settings.json | node_modules/.bin/mustache - src/settings-data.js.mustache >build/settings-data.js
cp src/unload-client.js build/unload-client.js
cp src/pdfjs-init.js build/pdfjs-init.js
cp -R src/vendor/pdfjs build/pdfjs
cp -R src/help/* build/help
cp -R src/images/* build/images
cp -R src/options/* build/options
  1. Zip all files in build directory into build.zip

  2. Attempt install of the file from the previous step on about:addons page.

image

I'm happy to include the zip file here, but did not want to accidentally pass on any sensitive data inside it.

robertknight commented 4 years ago

Hello,

The short answer is that you can't install a locally-built production Firefox extension locally at the moment via "Install add-on from file". The main issue you'll run into is that the extension won't be signed. We sign our add-ons as part of the build process so the solution will be for us to provide a signed version that you can download.

There is a way to disable that check, but it won't get it working because there are some other issues that need to be resolved:

  1. We don't set an extension ID in the manifest, which is required when installing an add-on from a local XPI file
  2. Logging in doesn't work, which is caused by the fact that the URL to the extension's files is randomized per installation.

You can get around issue 1 by loading the locally built extension as if you were developing it using the "Load Temporary Add-on" option at about:debugging#/runtime/this-firefox. However issue (2) will require some changes to how the extension and possibly client handle logging in, in order to work.

robertknight commented 4 years ago

Due to the signing issue mentioned above, we're never going to "fix" this issue so that loading a locally-built .xpi file just works in an unmodified Firefox. However when we resolve issues (1) and (2) above, which we'll have to do anyway in order to make a signed XPI work, that would make "Install add-on from file" work if you were to download the signed XPI from us and install that locally. If that worked, we could close this issue.

diegodlh commented 4 years ago

Hi, Robert. I understand that the OAuth server seems to respond with a fixed "origin" parameter depending on the client_id sent by the client. That is, "https://hypothes.is" for the bookmarklet client ID, "chrome-ext://" for the Chrome extension client ID, and "moz-extension://32492fee-2d9f-49fe-b268-fe213f7019f0" for the Firefox extension client ID. Issue 2 could be worked around by having the OAuth server respond with an "origin" parameter matching the "origin" parameter sent by the client. To make sure I understand correctly, the reason not to do this is to make sure that the client is legitimate? Do you have any alternative solutions in mind?

robertknight commented 4 years ago

To make sure I understand correctly, the reason not to do this is to make sure that the client is legitimate?

Yes, exactly. This is normal practice with OAuth for this reason. This is a fairly common need (you'll need it if you write an extension that uses Twitter, Facebook, Dropbox or Google APIs for example) so I'd guess there is a standard solution for this in the Chrome/WebExtension APIs, in which case its a case of figuring out how that works and how the Hypothesis browser extension can use it.

diegodlh commented 4 years ago

Hi, Robert. Thanks for answering. Please let me know if you think I should open a separate issue for this.

I understand the Hypothesis client would fall under the category of "Public" client type, "user-agent based application", according to RFC 6749, section 2.1.

I understand that what we are trying to avoid here would be "Client Impersonation", which is somehow covered in section 10.2 of the RFC.

However, client impersonation seems to be something difficult to prevent in Public clients (because they cannot use Client Secrets), and the RFC is quite vague as of how the authorization server should "protect resource owners [end-users] from such potentially malicious clients", and ultimately places responsibility on the user by advising that "the authorization server can engage the resource owner to assist in identifying the client and its origin".

The same assumption is expressed in:

The Hypothesis client uses the Web Message Response Mode, which I understand is still a draft, and its Security Consideration section has not been finished yet. However, it does say that the authorization server must only return white-listed target origins (section 5.2), which seems similar to what the Hypothesis server is doing (returning a fixed Target Origin value).

However, even this is easily circumventable. For example, I understand that a developer could come up with a malicious (unpublished) Chrome extension with the same extension id by declaring the same manifest key value. Or the Target Origin value in the Authorization Response could be easily tampered with by userscript managers (e.g., Greasemonkey) before window.opener.postMessage() is run.

Therefore, should it be accepted that client identity cannot be reliably verified, and hence have the authorization server return a Target Origin matching the Target Origin sent by the client, at least when client id is Firefox extension's?

Thanks!

robertknight commented 4 years ago

However, even this is easily circumventable. For example, I understand that a developer could come up with a malicious (unpublished) Chrome extension with the same extension id by declaring the same manifest key value. Or the Target Origin value in the Authorization Response could be easily tampered with by userscript managers (e.g., Greasemonkey) before window.opener.postMessage() is run.

Broadly speaking, the origin (protocol + host + port) is the fundamental boundary of the web's security model. This applies to many web APIs (eg. local storage, cookies, permissions, sending messages between iframes), not just OAuth. As a web developer who is prepared to enable "Developer mode" in Chrome, bypass security warnings and manipulate their system in other ways (eg. changing DNS resolution, installing custom SSL certificates system-wide), yes you can get around origin-based restrictions if you need to. However these options are not available to normal users who haven't taken these special measures (leaving aside phishing attacks, which it is considered the browser/OS's job to defend against, rather than the web application).

So for our purposes, we consider a redirect to a pre-registered URL or a postMessage to a pre-registered origin to be a safe way to deliver a message that can only reach a trusted client. This is not unique to Hypothesis, its how OAuth normally works for public clients.

From some brief searching, it looks like Chrome extensions / Firefox extensions have an API that exists specifically for the purpose of handling OAuth 2 login flows with a note that the redirect URL is designed to be suitable for pre-registering. I guess that we'll probably need to make use of that.

diegodlh commented 4 years ago

Hey, Robert. Thank you very much for providing such a detailed and informative response. I am really enthusiastic about Web Annotation and the Hypothesis' approach to it, which I have been following for the past year or so. I would like to contribute more to it, but sometimes lack of time (and of knowledge) get on the way. I apologize for having taken some of your time.

I learned about the API that you mentioned a few days ago and it looks promising. I understand this is not a priority for the Hypothesis team right now, so I will try to find some time in the following weeks to try something and share it with you.

robertknight commented 4 years ago

I apologize for having taken some of your time

No need to apologise 🙂. Its quite a useful exercise for me to think about and write down a response to this kind of question because you're not the only person to ask.

diegodlh commented 4 years ago

From some brief searching, it looks like Chrome extensions / Firefox extensions have an API that exists specifically for the purpose of handling OAuth 2 login flows with a note that the redirect URL is designed to be suitable for pre-registering. I guess that we'll probably need to make use of that.

A fix has been proposed here, and implemented in an unofficial extension (see discussion here) until an official extension is released.