FIDO U2F support for Safari
To use the extension:
- Download the latest release
- Run It
- Quit Safari (if it is open)
- Click
Open Safari Preferences
- Enable the
Safari FIDO U2F Extension
This extension requires macOS 10.12 and later, or macOS 10.11.5 and Safari 10.
The FIDO U2F specification defines a high-level javascript API, but leaves it up to the browser to implement it. It also leaves it up to individual sites to work out if the API is present / supported.
This extension works by injecting support for the high-level FIDO U2F Javascript API into the current page.
Many sites will notice that window.u2f
is present, and should just work. The following have been tested:
- Yubico Demo
- Google Demo
- AkiSec Demo
- u2f.bin.coffee Demo
- U2F Test Page
- Github Account Two-factor authentication
- Fastmail
There are two main reasons for sites not working:
-
The extension works by injecting code into to the page as it loads. Some sites perform their checks too early, before the injected code is present. Note that version 2.0 of the plugin now injects code quite early, and seems to have fixed most of these issues.
-
Some sites assume that only Chrome supports U2F on the Mac, and won't even try to use U2F if they detect Safari. As a workaround, changing Safari's User-Agent to Chrome may make these sites function. Unfortunately, other incompatibilities between Chrome and Safari can then often cause problems, since the site attempts to use unrelated Chrome-only features. Really the way to fix these sites is to talk to their developers and ask them to use another way to determine whether to enable their U2F support. Checking that
window.u2f
is non-null should be enough.
This extension uses the Safari App Extensions API introduced in Safari 10, which allows a Safari extension to be built using native code, and embedded in another app.
The native part is written in Swift, and uses libu2f-host to actually talk to the hardware.
A small bridge.js
script is injected into the top level page automatically by the extension.
This performs three tasks:
- listen for
beforeload
, and use it to inject another scriptu2f.js
into the document - listen for
u2f_
messages posted byu2f.js
, and passes them on to the native code - listen for messages sent back from the native code and pass them back to
u2f.js
for processing
The u2f.js
script implements the high-level JavaScript API described in the FIDO U2F Javascript API Specification.
It does this by setting window.u2f
to an object which provides implementations of u2f.register
, u2f.sign
and u2f.getApiVersion
. Both the API 1.1 and API 1.0 variants of register
and sign
are supported. If the page had already set window.u2f
, we attempt to merge our implementation into it, rather than completely replacing it. If we get there first, we try to lock down the window.u2f
property so that other scripts can't replace it with a different object (they can still set custom properties on it though). We also provide a non-standard u2f.isSafari
function which can be used to detect whether this particular implementation is present.
The high-level implementation converts the supplied parameters into a dictionary and sends this through for processing by the native code. The native code effectively implements the low-level API also described in the specification. In theory a site is free to talk directly to the native code by posting messages to it using the low-level API.
The high-level API is asynchronous, and returns its results via callbacks. A requestId
parameter is used to track requests sent to the extension. When replies come back from the extension, the same requestId
is used to associate the reply with the correct callback, which is then called.
As mentioned in problems above, the fact that we have to wait for the u2f.js
script to load can in theory cause timing problems. If a page has code which decides whether to enable U2F support based on whether window.u2f
is present, it's possible that it will run too early. We now inject u2f.js
by listening for the beforeload
event. This seems to get sent early enough (before DOMContentLoaded
) that most pages work.
- Clone this project
- Open Xcode workspace and select the scheme
application
- Extensions must be code signed. To build locally, you will need to adjust the
Development Team
setting of the project to a team that you have Mac Developer certificates for - Choose
Run
from theProduct
menu. - Xcode should build & launch the small app. You can then enable the extension from within Safari.
This plugin makes use of the following:
- hidapi: https://github.com/signal11/hidapi.git
- json-c: https://github.com/json-c/json-c.git
- u2f-host: https://github.com/Yubico/libu2f-host.git
For the sake of simplicity, compiled binaries for all three are committed to this repo.
The source for all three is available in the github repos above.
If you wish to build the libraries locally instead, you can do so using Homebrew, with brew install hidapi json-c libu2f-host
.
There are two sets of unit tests.
- The Swift tests can be run by choosing
Test
from theProduct
menu. These primarily test the code which formats incoming request dictionaries from the javascript side, and parses outgoing responses from the hardware side. We don't currently test the hardware itself, and therefore the tests can be run without hardware being present. - The javascript tests use Jest. You can install the dependencies for this from the command line with
npm install
. The tests can then be run withnpm test
.
CI is set up using Travis.
Committing branches or making pull requests in this repository will trigger builds of the tests. If you fork the repo, you will probably need to tweak .travis.yml
if you want to run the tests with your own instance.
There are a few things still to do. See the issues for more details.
If you find sites that don't work, or features that are missing feel free to make an issue to report them.
If you have the urge to contribute, that would be welcome. The code does not all follow a consistent standard yet but we're slowly cleaning it up.
By all means just hack on the code and make a pull request, but it might be best to check your plan first by making an issue and discussing it.
The authors of this extension are not security, cryptography or javascript experts.
This extension is still experimental, and use of it is entirely at your own risk!
All feedback and other contributions welcomed.
In particular, please tell us about sites that do / don't work! Please also consider contacting the owners of those sites to make them aware of this extension, so that we can work together to fix any problems.