Creating a Web-enabled USB Driver with WebUSB
2018-01-08 · Posted by: Santiago Torres-Arias · Categories: Informational · CommentsWebUSB is an emerging technology that opens numerous possibilities for interaction with hardware devices without the need to install any drivers on the user side. This could be very useful for playing web-based games (e.g., having a joystick for a game), utilizing web services (e.g., a 3D printer driver) or, as in the case described below, completing two-factor authentication with USB-enabled hardware tokens. Sadly, because it is an emerging technology, I found little documentation on how to write the appropriate code for a WebUSB driver. So, given that there are currently no applications supporting the development of these drivers, I decided to document the process of writing one for a YubiKey, using hash-based one-time-passwords (HOTP).
If you are curious as to why I initiated this effort, it grew out of a summer project here at NYU-SSL in which we used PolyPasswordHasher to support two factor authentication (2FA) by employing a Yubikey to increase the average entropy of the database. This, in turn, protects all the passwords. One of the goals that I had set for the summer was to create a demonstration website for a PPH-protected database. A user could register a yubikey at this site, and then log in using HOTP for 2FA.
Sadly, the ecosystem for browser USB extensions feels like a wasteland of deprecated or not-so-usable technologies:
- You could write a plugin, but that’s incredibly insecure and likely to be deprecated in one or two years. This is a very insecure option because the plugins run native code on the user’s system, sometimes without proper sandboxing.
- You could use Chrome’s USB extension library but, guess what, that’s also going to be deprecated in time. I personally wanted to avoid anything that would have to be reimplemented in just a year or two.
- You can try to ship a binary with a browser extension, but that would start more cross-platform compatibility problems than I can list here. I also don’t think it is reasonable to ask users to download some binary in order to use a demo website.
This leaves us with a somewhat experimental technology: WebUSB
Enter WebUSB
WebUSB is a standardized technology to provide a bridge for websites that can connect to user USB devices using JavaScript. You can look at it as if the website was providing you with a USB driver, along with the Javascript you usually send to, say, animate the elements on your website.
At first glance, this may sound like a security nightmare. Shipping code that can access a user’s hardware could be problematic. However, WebUSB is an improvement, security-wise, compared to the previous alternatives for the following reasons:
- The code is not running outside of a sandbox, like a plugin would be (e.g., flash).
- Permissions must be granted by the user to allow a website to access a USB device.
- Some devices, like USB keyboards, are not accessible to WebUSB (e.g., to
avoid keylogging)
That being said, I would still advise potential users to be wary of new technologies, as they need a thorough auditing before they can be trusted for a security-sensitive deployment. Issues are often found in early deployments of all technologies (take for example, this.
Besides the less-than-perfect security aspects of WebUSB, the only drawback that I found is a lack of documentation on how to write a WebUSB device handler. Here, I’ll document how I “reversed” the Yubikey ykchalresp binary and wrote a WebUSB driver for a Yubikey with HOTP enabled. (Note: Since the code is open-source, I could have just read it as is, but this may not always be the case. This exercise offers a way to write drivers when the code is not available.)
Setting up your dev environment
In order to develop for WebUSB, you need to move a few things around. First, you need the latest version of Chromium. Second, you need to run it with a couple of flags and a local web server to serve your WebUSB JavaScript flies. Third, you may need to enable a couple of flags within Chromium to enable experimental features.
Start Chromium like this:
$ chromium --disable-web-security --allow-insecure-localhost
We’ll be serving the files using a plain http server from Python (though you can use any server you may feel comfortable with to host the files). By default, WebUSB is not enabled if the content is not served through HTTPS with a trusted certificate (++ for security here). A complete list of flags can be taken from this site, in case you’re curious, although you will not need more than these two.
Finally, depending on the age of your version of Chromium, you may need to enable the experimental features by navigating to https://peter.sh/experiments/chromium-command-line-switches/ and enabling a flag called “Experimental web platform features.” If you have done this, then you will need to restart your browser.
After setting up Chromium, you can start serving your local WebUSB files like so:
$ python3 -m http.server
Cool! Now you should be able to navigate to localhost and play around with WebUSB and your device.
Sniffing the USB device
Another necessary task is to understand what the original USB driver is sending to the device in order to replicate it. Although you may want to write something that already has an implementation using other libraries (e.g.,libusb), or a specification describing these tasks, you may run into devices that are not documented. (Again, this was not the case with the YubiKey device, but, as you recall this is also an exercise in reversing an undocumented protocol, so we proceed as if there was no specification). If there are no documents on how to interact with your device, a simple pcap using Wireshark can work wonders.
Setting up Wireshark for USB sniffing
To sniff USB traffic under Linux, a user needs to load a few modules and change a few permissions in Wireshark. The instructions are taken from this article article, but I’ll inline the Linux instructions here for the sake of readability:
First, load the usbmon kernel module:
# modprobe usbmon
This will create a series of /dev/usbmonN devices. You need to make them readable by regular users:
$ sudo setfacl -m u:$USER:r /dev/usbmon*
Having done this, you can launch Wireshark and pick an interface to sniff. The one to pick can be easily seen using dmesg. Launch dmesg on follow and then plug in your device. You should see something like this:
[ 6350.949823] usb 1-4: new full-speed USB device number 9 using xhci_hcd
[ 6351.093360] input: Yubico Yubikey NEO OTP+CCID as /devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4:1.0/0003:1050:0111.0006/input/input22
[ 6351.150902] hid-generic 0003:1050:0111.0006: input,hidraw0: USB HID v1.10 Keyboard [Yubico Yubikey NEO OTP+CCID] on usb-0000:00:14.0-4/input0
The important things to note about this part of the log is usb 1-4. This means it was connected to the usbmon1 interface. You can also know what “address” Wireshark will use from the information on the rest of the line (1.9.x). A sample Wireshark capture of a packet going to our USB device would look like this:
398 19.946717 host 1.9.0 USB 72 URB_CONTROL out
This was a packet sent from a laptop into 1.9.0, the device that was just connected. Using Wireshark, we can capture the “conversation” between the laptop and the yubikey (or any other device), and be aware of what is being sent and received.
In this case, the host sends a series of URB_CONTROL out message(s) with certain flags and the challenge to hash, waits for a status flag to be set on the replies, and starts reading the resulting HOTP hash. You can see the relevant bits of the conversation on packets 8 to 47 in this pcap.
Translating sniffed packets into WebUSB calls
Now that we know what we need to do, we can try to replicate the behavior using WebUSB to interact with our devices. For example, the details of the packet listed above are as follows:
This can be translated into the following WebUSB call:
Device.controlTransferOut({
"recipient": "interface",
"requestType": "class",
"request": 9,
"value": 0x0300,
"index": 0
}, Data);
You may suspect that some of the values on the Wireshark scan are the same as the arguments sent to the control transfer out. Well, it is that simple. If you don’t want to understand what these values mean (I certainly won’t cover them here), you could just blindly build the same request and see how the device behaves.
These calls return a promise object, which resolves with the data the device contains after our call. We would have to chain these promises to effectively have a conversation with our yubikey. However, this may not be as straightforward as with other approaches.
Fun with promises
The WebUSB API relies on promises, which make writing driver-like (e.g., polling and/or synchronous) code a little weird. This is because WebUSB is merging two worlds: one being the weird JavaScript “asynchronicity” on the web-space, and the other, the structured-protocol, raw-byte-handling world of low-level device interaction. This construction will often lead to a design pattern: nested-promises.
A nested WebUSB promise, in simple terms, is something that does the following:
- Start a promise by sending a request. The result will be handled by another promise
- The second promise will check whether the request is ready (i.e., the read (i.e., the read
frame says “good to go”)
- a. If it’s not ready, start another promise exactly like the one in step
- b. If it is ready, then move on and resolve the “outer” promise, so we can continue onward to the next step.
This may be easier to picture in the diagram below.
This construction makes it so that the outer promise can construct a promise chain that follows a structured protocol, such as the one used by the YubikeyHOTP interface. In contrast, the inner promise chain will make the outer promises hold on until the device is ready for the next step.
This way, we can write a USB device handler that looks pretty much like the drivers you would write with libusb, but can do so using the async and pretty JavaScript-y API of WebUSB.
Writing WebUSB handlers/drivers is rather fun and easy once you get started. Reversing usb devices is a fun side-project that may keep you interested/busy for a good week, while learning a little about the things we plug into our computers every day.