Compare commits

21 Commits

Author SHA1 Message Date
b0d990f708 Added authorisation. 2019-09-13 18:52:37 +01:00
8a62dee131 Fix relying party verification.
Fix to throw error when relying party uses the wrong application
parameter when requesting a signature.
2019-09-11 15:48:28 +01:00
4dac7dd014 Allow on-demand file loading and unloading. 2019-09-10 21:42:04 +01:00
3cc7032cf4 Fixed typo in Certificates.hpp.
Updated GenCErtificates.sh script to accomodate change.
2019-09-10 17:22:58 +01:00
152bc721e1 Implemented automated certificate generation.
Removed existing certificates as private key was shared.
Updated makefile to require certificate generation.
2019-09-10 17:22:58 +01:00
89b4b7d949 Additional error handling for rarer states.
Ensure std::map::at is not blindly applied without checking that the key
actually exists.

Fixes crashes where u2f client on PC/browser expects channel open, but
service has been restarted.
2019-09-10 17:22:52 +01:00
f8d077634e Fix missing signal handling.
Plug signal handler into additional signal. Fixes no cleanup (corruption)
on stopping.

May impact #6.
2019-09-10 10:31:54 +01:00
c14c6d0f0d Fix incorrect paths for debug files. 2019-09-10 10:31:06 +01:00
1497e620c1 fixup! Fix host-descriptor deleter specialisation. 2019-09-07 16:43:16 +01:00
5921e6ea48 Fix host-descriptor deleter specialisation. 2019-09-06 13:41:40 +01:00
dac4617d70 Use client-server processing to avoid constant super-user invocation. 2019-09-05 21:37:33 +01:00
01dad2ee0a Reformatting. 2019-08-23 13:27:30 +01:00
03dba21ad4 Update gitignore.
Added clang-formatting specification.
2019-08-23 13:26:06 +01:00
0606772cc3 Basic U2FDevice implementation. 2019-08-23 13:00:22 +01:00
2987cbe26e Separated waiting for message from acting on message.
Necessary refactoring of parameter types propagated through source.
2019-08-21 15:55:33 +01:00
2f8e417d00 Fix building on Android NDK. 2019-08-18 17:22:01 +01:00
87ac9c5946 Added utility functions to load keys from streams. 2019-08-18 17:06:23 +01:00
4ffdeb3eda Hopefully improved attestation certificate creation README.
Fixes #3.
2019-08-05 13:07:07 +01:00
f3fa1d29ba Replaced incorrect #if with #ifdef.
Fixes #2.
2019-08-05 13:07:07 +01:00
0523b49c72 Added .report_length explanation. 2019-06-26 17:05:13 +01:00
e6739cc5af Added android build descriptions. 2019-06-26 16:49:38 +01:00
37 changed files with 910 additions and 369 deletions

25
Architecture.cpp Normal file
View File

@@ -0,0 +1,25 @@
/*
U2FDevice - A program to allow Raspberry Pi Zeros to act as U2F tokens
Copyright (C) 2018 Michael Kuc
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "_Architecture.hpp"
using namespace std;
#if ARCHITECTURE == ARCH_ANDROID
string hidDev = "/dev/hidg2";
#endif

View File

@@ -18,22 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once #pragma once
#define ARCH_RASPBERRY_PI 1 #include "_Architecture.hpp"
#define ARCH_ANDROID 2
#define ARCHITECTURE ARCH_RASPBERRY_PI
#if ARCHITECTURE == ARCH_RASPBERRY_PI
# define STORAGE_PREFIX "/usr/share/"
# define HID_DEV "/dev/hidg0"
# define DEBUG_STREAMS "/tmp/"
// #define DEBUG_MSGS
# define LEDS
#elif ARCHITECTURE == ARCH_ANDROID
# define STORAGE_PREFIX "/sdcard/U2F/"
# define HID_DEV "/dev/hidg2"
# define DEBUG_STREAMS "/data/local/tmp/"
// #define DEBUG_MSGS
#endif
#undef ARCH_ANDROID #undef ARCH_ANDROID
#undef ARCH_RASPBERRY_PI #undef ARCH_RASPBERRY_PI

View File

@@ -17,11 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "Channel.hpp" #include "Channel.hpp"
#include "Storage.hpp"
#include "U2F_CMD.hpp" #include "U2F_CMD.hpp"
#include "u2f.hpp" #include "u2f.hpp"
#include <iostream> #include <iostream>
#include <stdexcept> #include <stdexcept>
#include <android/log.h>
using namespace std; using namespace std;
@@ -34,10 +34,10 @@ uint32_t Channel::getCID() const {
return cid; return cid;
} }
void Channel::handle(const shared_ptr<U2FMessage> uMsg) { bool Channel::handle(const U2FMessage& uMsg, AuthorisationLevel auth) {
if (uMsg->cmd == U2FHID_INIT) if (uMsg.cmd == U2FHID_INIT)
this->initState = ChannelInitState::Initialised; this->initState = ChannelInitState::Initialised;
else if (uMsg->cid != this->cid) else if (uMsg.cid != this->cid)
throw runtime_error{ "CID of request invalid for this channel" }; throw runtime_error{ "CID of request invalid for this channel" };
if (this->initState != ChannelInitState::Initialised) if (this->initState != ChannelInitState::Initialised)
@@ -47,12 +47,21 @@ void Channel::handle(const shared_ptr<U2FMessage> uMsg) {
auto cmd = U2F_CMD::get(uMsg); auto cmd = U2F_CMD::get(uMsg);
if (cmd) { if (!cmd)
cmd->respond(this->cid); return true;
if (cmd->modifiesPersistentState()) if (cmd->requiresAuthorisation()) {
Storage::save(); if (auth == AuthorisationLevel::Unspecified) {
__android_log_print(ANDROID_LOG_DEBUG, "U2FDevice",
"Action requires authorisation; seeking authorisation");
return false;
}
else if (auth == AuthorisationLevel::Unauthorised)
__android_log_print(ANDROID_LOG_DEBUG, "U2FDevice", "Action requires authorisation; authorisation not granted");
} }
cmd->respond(this->cid, auth == AuthorisationLevel::Authorised);
return true;
} }
void Channel::init(const ChannelInitState newInitState) { void Channel::init(const ChannelInitState newInitState) {

View File

@@ -25,6 +25,8 @@ enum class ChannelInitState { Unitialised, Initialised };
enum class ChannelLockedState { Locked, Unlocked }; enum class ChannelLockedState { Locked, Unlocked };
enum class AuthorisationLevel { Unspecified, Unauthorised, Authorised };
class Channel { class Channel {
protected: protected:
uint32_t cid; uint32_t cid;
@@ -33,7 +35,10 @@ protected:
public: public:
Channel(const uint32_t channelID); Channel(const uint32_t channelID);
void handle(const std::shared_ptr<U2FMessage> uMsg);
// Returns false if requires authorisation check
// True otherwise
bool handle(const U2FMessage& uMsg, AuthorisationLevel auth);
uint32_t getCID() const; uint32_t getCID() const;
void init(const ChannelInitState newInitState); void init(const ChannelInitState newInitState);

View File

@@ -27,6 +27,15 @@ using namespace std;
Controller::Controller(const uint32_t startChannel) : channels{}, currChannel{ startChannel } {} Controller::Controller(const uint32_t startChannel) : channels{}, currChannel{ startChannel } {}
void Controller::handleTransaction() { void Controller::handleTransaction() {
auto msg = U2FMessage::readNonBlock();
if (!msg)
return;
handleTransaction(*msg, AuthorisationLevel::Unspecified);
}
bool Controller::handleTransaction(const U2FMessage& msg, AuthorisationLevel auth) {
try { try {
if (channels.size() != 0 && if (channels.size() != 0 &&
chrono::duration_cast<chrono::seconds>(chrono::system_clock::now() - lastMessage) < chrono::duration_cast<chrono::seconds>(chrono::system_clock::now() - lastMessage) <
@@ -37,19 +46,13 @@ void Controller::handleTransaction() {
} catch (runtime_error& ignored) { } catch (runtime_error& ignored) {
} }
auto msg = U2FMessage::readNonBlock();
if (!msg)
return;
lastMessage = chrono::system_clock::now(); lastMessage = chrono::system_clock::now();
auto opChannelID = msg->cid; auto opChannelID = msg.cid;
if (msg->cmd == U2FHID_INIT) { if (msg.cmd == U2FHID_INIT) {
opChannelID = nextChannel(); opChannelID = nextChannel();
auto channel = Channel{ opChannelID }; auto channel = Channel{ opChannelID };
try { try {
channels.emplace(opChannelID, channel); // In case of wrap-around replace existing one channels.emplace(opChannelID, channel); // In case of wrap-around replace existing one
} catch (...) { } catch (...) {
@@ -57,15 +60,15 @@ void Controller::handleTransaction() {
} }
} else if (channels.find(opChannelID) == channels.end()) { } else if (channels.find(opChannelID) == channels.end()) {
U2FMessage::error(opChannelID, ERR_CHANNEL_BUSY); U2FMessage::error(opChannelID, ERR_CHANNEL_BUSY);
return; return true;
} }
#ifdef DEBUG_MSGS #ifdef DEBUG_MSGS
clog << "Message:" << endl; clog << "Message:" << endl;
clog << "cid: " << msg->cid << ", cmd: " << static_cast<unsigned int>(msg->cmd) << endl; clog << "cid: " << msg.cid << ", cmd: " << static_cast<unsigned int>(msg.cmd) << endl;
#endif #endif
channels.at(opChannelID).handle(msg); return channels.at(opChannelID).handle(msg, auth);
} }
uint32_t Controller::nextChannel() { uint32_t Controller::nextChannel() {

View File

@@ -31,5 +31,9 @@ public:
Controller(const uint32_t startChannel = 1); Controller(const uint32_t startChannel = 1);
void handleTransaction(); void handleTransaction();
// Returns false if required authentication
// Returns true otherwise
bool handleTransaction(const U2FMessage& msg, AuthorisationLevel auth);
uint32_t nextChannel(); uint32_t nextChannel();
}; };

85
IO.cpp
View File

@@ -17,30 +17,43 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "IO.hpp" #include "IO.hpp"
#include "Streams.hpp"
#include <iostream>
#include <unistd.h>
//#include <sys/ioctl.h>
#include "Macro.hpp" #include "Macro.hpp"
#include "Streams.hpp"
#include "U2FDevice.hpp" #include "U2FDevice.hpp"
#include "u2f.hpp" #include "u2f.hpp"
#include <android/log.h>
#include <array>
#include <chrono> #include <chrono>
#include <cstdio>
#include <ctime>
#include <fcntl.h> #include <fcntl.h>
#include <iostream>
#include <memory>
#include <poll.h>
#include <ratio> #include <ratio>
#include <stdexcept>
#include <string>
#include <sys/socket.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
using namespace std; using namespace std;
bool bytesAvailable(const size_t count); bool bytesAvailable(size_t count);
vector<uint8_t>& getBuffer(); vector<uint8_t>& getBuffer();
#ifdef DEBUG_STREAMS
string cacheDirectory{ DEBUG_STREAMS };
#endif
#ifdef HID_SOCKET
string clientSocket{};
#endif
vector<uint8_t> readNonBlock(const size_t count) { vector<uint8_t> readNonBlock(const size_t count) {
if (!bytesAvailable(count)) { if (!bytesAvailable(count))
return vector<uint8_t>{}; return vector<uint8_t>{};
}
auto& buffer = getBuffer(); auto& buffer = getBuffer();
auto buffStart = buffer.begin(), buffEnd = buffer.begin() + count; auto buffStart = buffer.begin(), buffEnd = buffer.begin() + count;
@@ -52,12 +65,13 @@ vector<uint8_t> readNonBlock(const size_t count) {
return bytes; return bytes;
} }
void write(const uint8_t* bytes, const size_t count) { void write(const vector<uint8_t>& bytes) {
size_t totalBytes = 0; size_t totalBytes = 0;
auto hostDescriptor = *getHostDescriptor(); auto hostDescriptor = *getHostDescriptor();
while (totalBytes < count) { while (totalBytes < bytes.size()) {
auto writtenBytes = write(hostDescriptor, bytes + totalBytes, count - totalBytes); auto writtenBytes =
send(hostDescriptor, bytes.data() + totalBytes, bytes.size() - totalBytes, 0);
if (writtenBytes > 0) if (writtenBytes > 0)
totalBytes += writtenBytes; totalBytes += writtenBytes;
@@ -71,18 +85,16 @@ void write(const uint8_t* bytes, const size_t count) {
bool bytesAvailable(const size_t count) { bool bytesAvailable(const size_t count) {
auto startTime = std::chrono::high_resolution_clock::now(); auto startTime = std::chrono::high_resolution_clock::now();
const timespec iterDelay{ 0, 10000000 };
chrono::duration<double, milli> delay{ 0 }; chrono::duration<double, milli> delay{ 0 };
while (delay.count() < U2FHID_TRANS_TIMEOUT && contProc) { while (delay.count() < U2FHID_TRANS_TIMEOUT) {
delay = chrono::high_resolution_clock::now() - startTime;
if (getBuffer().size() >= count) { if (getBuffer().size() >= count) {
#ifdef DEBUG_MSGS #ifdef DEBUG_MSGS
clog << "Requested " << count << " bytes" << endl; clog << "Requested " << count << " bytes" << endl;
#endif #endif
return true; return true;
} }
nanosleep(&iterDelay, nullptr); delay = chrono::high_resolution_clock::now() - startTime;
} }
#ifdef DEBUG_MSGS #ifdef DEBUG_MSGS
@@ -98,39 +110,48 @@ vector<uint8_t>& bufferVar() {
} }
vector<uint8_t>& getBuffer() { vector<uint8_t>& getBuffer() {
const timespec delay{ 0, 10'000'000 };
auto& buff = bufferVar(); auto& buff = bufferVar();
array<uint8_t, HID_RPT_SIZE> bytes{}; array<uint8_t, HID_RPT_SIZE> bytes{};
auto hostDescriptor = *getHostDescriptor(); auto hostDescriptor = *getHostDescriptor();
pollfd pFD{ hostDescriptor, POLLIN };
while (true) { while (true) {
auto readByteCount = read(hostDescriptor, bytes.data(), HID_RPT_SIZE); ppoll(&pFD, 1, &delay, 0);
if (readByteCount > 0 && readByteCount != HID_RPT_SIZE) { if (pFD.revents & POLLIN) {
// Failed to copy an entire packet in, so log this packet auto readByteCount = recv(hostDescriptor, bytes.data(), HID_RPT_SIZE, 0);
if (readByteCount > 0 && readByteCount != HID_RPT_SIZE) {
// Failed to copy an entire packet in, so log this packet
#ifdef DEBUG_MSGS #ifdef DEBUG_MSGS
cerr << "Only retrieved " << readByteCount << " bytes from expected full packet." cerr << "Only retrieved " << readByteCount << " bytes from expected full packet."
<< endl; << endl;
#endif #endif
} }
if (readByteCount > 0) { if (readByteCount > 0) {
copy(bytes.begin(), bytes.begin() + readByteCount, back_inserter(buff)); copy(bytes.begin(), bytes.begin() + readByteCount, back_inserter(buff));
#ifdef DEBUG_STREAMS #ifdef DEBUG_STREAMS
fwrite(bytes.data(), 1, readByteCount, getComHostStream().get()); fwrite(bytes.data(), 1, readByteCount, getComHostStream().get());
#endif #endif
} else if (errno != EAGAIN && errno != EWOULDBLOCK) // Expect read would block } else if (errno != EAGAIN && errno != EWOULDBLOCK) // Expect read would block
{ {
ERR(); ERR();
#ifdef DEBUG_MSGS #ifdef DEBUG_MSGS
cerr << "Unknown stream error: " << errno << endl; cerr << "Unknown stream error: " << errno << endl;
#endif #endif
break;
} else {
errno = 0;
break; // Escape loop if blocking would occur
}
} else
break; break;
} else {
errno = 0;
break; // Escape loop if blocking would occur
}
} }
return buff; return buff;

10
IO.hpp
View File

@@ -21,9 +21,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#ifdef DEBUG_STREAMS
extern std::string cacheDirectory;
#endif
#ifdef HID_SOCKET
extern std::string clientSocket;
#endif
// Returns either the number of bytes specified, // Returns either the number of bytes specified,
// or returns empty vector without discarding bytes from HID stream // or returns empty vector without discarding bytes from HID stream
std::vector<uint8_t> readNonBlock(const size_t count); std::vector<uint8_t> readNonBlock(const size_t count);
// Blocking write to HID stream - shouldn't block for too long // Blocking write to HID stream - shouldn't block for too long
void write(const uint8_t* bytes, const size_t count); void write(const std::vector<uint8_t>& data);

View File

@@ -237,7 +237,7 @@ void InitPacket::writePacket() {
memcpy(this->buf + 5, &bcnth, 1); memcpy(this->buf + 5, &bcnth, 1);
memcpy(this->buf + 6, &bcntl, 1); memcpy(this->buf + 6, &bcntl, 1);
memcpy(this->buf + 7, data.data(), data.size()); memcpy(this->buf + 7, data.data(), data.size());
write(this->buf, sizeof(this->buf)); write(vector<uint8_t>{ this->buf, this->buf + sizeof(this->buf) });
#ifdef DEBUG_STREAMS #ifdef DEBUG_STREAMS
fwrite(this->buf, 1, sizeof(this->buf), devStream); fwrite(this->buf, 1, sizeof(this->buf), devStream);
@@ -283,7 +283,7 @@ void ContPacket::writePacket() {
memcpy(this->buf + 4, &seq, 1); memcpy(this->buf + 4, &seq, 1);
memcpy(this->buf + 5, data.data(), data.size()); memcpy(this->buf + 5, data.data(), data.size());
write(this->buf, HID_RPT_SIZE); write(vector<uint8_t>{ this->buf, this->buf + HID_RPT_SIZE });
#ifdef DEBUG_STREAMS #ifdef DEBUG_STREAMS
fwrite(this->buf, HID_RPT_SIZE, 1, devStream); fwrite(this->buf, HID_RPT_SIZE, 1, devStream);

View File

@@ -1,9 +1,3 @@
# Automatically generate keys
Run `./GenCertificates.sh`, answering the prompt to produce your own certificate.
# Manually generate keys
From [Teensy U2F](https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp) line 292 From [Teensy U2F](https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp) line 292
Instructions to generate attestation certificate using open ssl Instructions to generate attestation certificate using open ssl

417
Readme.md
View File

@@ -1,133 +1,232 @@
# U2FDevice # U2FDevice
For the Android version of this, please see [the android branch](../../tree/android), as there is a completely different build procedure and resources. This program uses an [Android smartphone](https://www.android.com/intl/en_uk/) to act as a [U2F token](https://www.yubico.com/solutions/fido-u2f/). This permits any person with a compatible Android phone to try out U2F 2FA for themselves.
This program uses a Raspberry Pi 0 to act as a [U2F token](https://www.yubico.com/solutions/fido-u2f/). This permits any person with a Raspberry Pi 0 to try out U2F 2FA for themselves. All testing has been performed on a [Nexus 5X](https://en.wikipedia.org/wiki/Nexus_5X).
# Required materials # Required materials
![Raspberry Pi Zero](https://www.raspberrypi.org/app/uploads/2017/05/Raspberry-Pi-Zero-462x322.jpg "Raspberry Pi 0") ![Android smartphone](https://upload.wikimedia.org/wikipedia/commons/4/40/Nexus_5X_%28White%29.jpg "Nexus 5X")
![SD Card with capacity > 2GB](https://upload.wikimedia.org/wikipedia/commons/d/da/MicroSD_cards_2GB_4GB_8GB.jpg "SD Card with capacity > 2GB")
![Computer](https://upload.wikimedia.org/wikipedia/commons/f/fa/BackSlash_Linux_Olaf_Laptop_Dekstop.png "Preferably Linux computer") ![Computer](https://upload.wikimedia.org/wikipedia/commons/f/fa/BackSlash_Linux_Olaf_Laptop_Dekstop.png "Preferably Linux computer")
![Confirmed-working micro USB data cable](Readme_Assets/Micro_USB_Cable.png "Micro USB Data Cable") ![Confirmed-working USB data cable](Readme_Assets/Micro_USB_Cable.png "Micro USB Data Cable")
# OS Install # OS Install
## Getting the image ## Getting the ROM
Go [here](https://www.raspberrypi.org/downloads/raspbian/) and download the Raspbian Stretch _Lite_ image.<br />If you download the zip, make sure you extract it. Choose whichever [ROM](https://www.xda-developers.com/what-is-custom-rom-android/) you want **for your device**. Make sure that the kernel source is available for it.
## Installing the image For example, using the [Nexus 5X](https://en.wikipedia.org/wiki/Nexus_5X), I chose [CypherOS 7](https://forum.xda-developers.com/nexus-5x/development/rom-cypheros-7-0-0-poundcake-t3869446).
Once you have the `*.img` file, you must write it to an SD card. <br />For this, I suggest the use of [etcher.io](https://etcher.io/) which works cross-platfrom. Alternatively, use whatever image burner you want. ## Installing the ROM
## Mounting the SD Card Install TWRP. See [here](https://www.xda-developers.com/how-to-install-twrp/) for details.
Install ROM. See [here](https://www.xda-developers.com/how-to-install-custom-rom-android/) for details.
Install a root utility. Note that there are multiple options, but I recommend [Magisk](https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445), with details on how to install [here](https://www.xda-developers.com/how-to-install-magisk/) also.
After the image burning has completed, most OSs should automatically mount the SD card. We only need access to the `boot` partition initially. ## Booting the device
![Storage Monitor](Readme_Assets/Storage.png) Check the device boots now, by rebooting from TWRP. It is possible to skip this step, but don't. This gives you the assurance that the ROM actually works on your device / you performed the install steps correctly.
## Setting up SSH ## Getting the kernel
Since this device can be set up without a monitor, we shall. <br />First, we need to enable SSH. Note, if using a Nexus 5X with the same ROM, you can skip ahead, as a pre-built boot image has already been prepared, from the source available [here](https://github.com/Crystalix007/kernel_lge_bullhead).
1. Open the boot partition - it should look like so: <br />![Boot Partition](Readme_Assets/Boot_partition.png)
2. Create a `ssh` file. Note that this doesn't have any extension. For Linux users, you can open a console in this directory, and `touch ssh` to create the file. This file enables `ssh` on the latest versions of Raspbian. <br />![ssh file](Readme_Assets/Create_ssh.png) 1. From the place where you got your ROM, there should be an explanation of where to get the corresponding kernel source. Most often, this is hosted on [Github](https://github.com/).
Alternatively, for stock ROMS, seek out your manufacturer's website for the source code, for example [LG Open Source](http://opensource.lge.com/index).
3. Edit `config.txt`. Using your preferred editor, add the line `dtoverlay=dwc2` to the end. <br />![Edited config.txt](Readme_Assets/Edit_config_txt.png) 2. On a Linux (virtual) machine, [`git clone`](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository#_git_cloning) this source / download it and unpack it.
4. Edit `cmdline.txt`. Again, using your preferred editor, between `rootwait` and the next word, insert `modules-load=dwc2,g_ether` leaving only one space after `rootwait` and one before the next word. <br />![Edited cmdline.txt](Readme_Assets/Edit_cmdline_txt.png) 3. Build the kernel from source to check the build works ([paraphrased from xda-developers](https://forum.xda-developers.com/android/software-hacking/reference-how-to-compile-android-kernel-t3627297)).
* install a cross-compiler for [your device's **architecture**](https://wiki.lineageos.org/devices/).
* setup environment to target cross-compiler: `export CROSS_COMPILE=<compiler-prefix->`, where if your cross-compiler's `gcc` is `/usr/bin/aarch64-linux-android-gcc`, then the prefix is `/usr/bin/aarch64-linux-android-`.
* setup environment to target correct architecture: `export ARCH=<arch> && export SUBARCH=<arch>`. Here, `<arch>` can be `arm`, `arm64`, `x86`, etc.
* setup a Python 2 virtualenv: `virtualenv2 kernelbuild && source kernelbuild/bin/activate`
* find your defconfig. This will be inside `arch/<arch>/configs`, and have a name like `<codename>_defconfig`, e.g. `bullhead_defconfig` for the Nexus 5X. If you cannot find the correct file, look up your specific device.
* run:
* `make clean`
* `make mrproper`
* `make <defconfig_name>`
* `make -j$(nproc --all)`
* if, during the build, it fails due to a missing program or library, install, retry the command, and continue.
5. Eject the SD card safely. <br />![Eject SD card](Readme_Assets/Eject_SD.png) 4. Build a replacement `boot.img` to flash in TWRP:
* download the latest Android Image Kitchen (AIK) script from [this thread](https://forum.xda-developers.com/showthread.php?t=2073775), and unzip outside the kernel source.
* take the existing `boot.img` from your chosen ROM (try looking inside the zip file), and copy it to the unpacked AIK folder.
* run `./unpackimg.sh <your-boot-img>.img`.
* copy your built kernel called `Image`, `Image-dtb`, `Image.gz` or `Image.gz-dtb`, from `arch/<arch>/boot` in the kernel folder, to the `split_img` subfolder inside the AIK folder.
* navigate into the `split_img` folder, and replace the old `boot.img-zImage` with your compiled kernel.
* navigate up a folder, to the AIK folder, and run `./repackimg.sh` to build an `image-new.img` image.
6. Insert the SD card into the Raspberry Pi. <br />![Inserted SD card](Readme_Assets/Insert_SD.png) 5. Install the compiled kernel, to check the compiled kernel still works:
* reboot your device to TWRP.
* copy the `image-new.img` to your device.
* install from TWRP (you can press the `Install Image` button in the install menu to see images) to the boot partition.
* re-install Magisk (or whichever root utility you choose), as it was likely lost when you flashed the new kernel
7. Use a known USB micro data cable to connect the Raspberry Pi to a computer. 6. Reboot to ensure the device still works.
* If possible, check this cable can transfer data by connecting another device and attempting to transfer a file. 7. Find the required patch from [pelya/android-keyboard-gadget](https://github.com/pelya/android-keyboard-gadget). You will want to look in the subdirectory patches for a patch matching your device's description. If you cannot find your device, you will have to try applying one of the [generic patches according to your kernel version](https://github.com/pelya/android-keyboard-gadget/tree/master/patches/existing_tested/by-generic-kernel-version).
* If possible, use a Linux computer for the next step, one with a graphical network manager.
* If possible, use a USB 3 or higher port instead of a USB 2 port. This is because the USB port will supply the entire power for the Raspberry Pi, and some USB 2 ports may not be able to deliver the required power.
8. Let the Raspberry Pi fully boot up (once the LED stops blinking frequently, it is probably booted). Then you need to configure your network settings. To apply a patch, download it to the kernel directory, and run `git apply name-of-file.patch` if you cloned the directory, or `patch -p1 < name-of-file.patch` if you downloaded the kernel.
* Linux: 8. Modify your kernel to support U2F:
* using the previous patch for guidance, apply a U2F patch [also available here](Scripts/u2f-kernel.patch)
* Use your preferred network manager: ```diff
diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c
index ced63c82ae7..41ac7ac18ca 100644
--- a/drivers/usb/gadget/android.c
+++ b/drivers/usb/gadget/android.c
@@ -55,20 +55,21 @@
#include "u_ctrl_hsic.c"
#include "u_data_hsic.c"
#include "u_ctrl_hsuart.c"
#include "u_data_hsuart.c"
#include "f_ccid.c"
#include "f_mtp.c"
#include "f_accessory.c"
#include "f_hid.h"
#include "f_hid_android_keyboard.c"
#include "f_hid_android_mouse.c"
+#include "f_hid_android_u2f.c"
#include "f_rndis.c"
#include "rndis.c"
#include "f_qc_ecm.c"
#include "f_mbim.c"
#include "f_qc_rndis.c"
#include "u_data_ipa.c"
#include "u_bam_data.c"
#include "f_ecm.c"
#include "u_ether.c"
#include "u_qc_ether.c"
@@ -2855,21 +2856,21 @@ static int uasp_function_bind_config(struct android_usb_function *f,
* Create a new connection profile <br />![Creating connection profile](Readme_Assets/Add_connection.png) static struct android_usb_function uasp_function = {
.name = "uasp",
.init = uasp_function_init,
.cleanup = uasp_function_cleanup,
.bind_config = uasp_function_bind_config,
};
* Set it's type to be 'Wired Ethernet (shared)' if available, else, 'Wired Ethernet' static int hid_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev)
{
- return ghid_setup(cdev->gadget, 2);
+ return ghid_setup(cdev->gadget, 3);
}
* Name it <br />![Naming new connection](Readme_Assets/Configure_Connection_Name.png) static void hid_function_cleanup(struct android_usb_function *f)
{
ghid_cleanup();
}
* Ensure the method is set to 'Shared to other computers', or similar. <br />![Sharing connection to other computers](Readme_Assets/Configure_Connection_Method.png) static int hid_function_bind_config(struct android_usb_function *f, struct usb_configuration *c)
{
int ret;
@@ -2878,20 +2879,26 @@ static int hid_function_bind_config(struct android_usb_function *f, struct usb_c
if (ret) {
pr_info("%s: hid_function_bind_config keyboard failed: %d\n", __func__, ret);
return ret;
}
printk(KERN_INFO "hid mouse\n");
ret = hidg_bind_config(c, &ghid_device_android_mouse, 1);
if (ret) {
pr_info("%s: hid_function_bind_config mouse failed: %d\n", __func__, ret);
return ret;
}
+ printk(KERN_INFO "hid u2f\n");
+ ret = hidg_bind_config(c, &ghid_device_android_u2f, 2);
+ if (ret) {
+ pr_info("%s: hid_function_bind_config u2f failed: %d\n", __func__, ret);
+ return ret;
+ }
return 0;
}
* Save and connect on the connection not currently used. <br />![Connecting to new connection](Readme_Assets/Connect_to_connection.png) static struct android_usb_function hid_function = {
.name = "hid",
.init = hid_function_init,
.cleanup = hid_function_cleanup,
.bind_config = hid_function_bind_config,
};
* Check your IP address on this network. <br />![Checking IP address](Readme_Assets/Checking_IP_Address.png) diff --git a/drivers/usb/gadget/f_hid.c b/drivers/usb/gadget/f_hid.c
index 155d9fb3a08..117bc00feac 100644
--- a/drivers/usb/gadget/f_hid.c
+++ b/drivers/usb/gadget/f_hid.c
@@ -57,21 +57,21 @@ struct f_hidg {
int minor;
struct cdev cdev;
struct usb_function func;
* This shows that the host computer (Linux) has the IP address `10.42.0.1` struct usb_ep *in_ep;
struct usb_ep *out_ep;
};
* Therefore, the Raspberry Pi has an IP address of `10.42.0.*`, where `*` can be any number 2-255. /* Hacky device list to fix f_hidg_write being called after device destroyed.
It covers only most common race conditions, there will be rare crashes anyway. */
-enum { HACKY_DEVICE_LIST_SIZE = 4 };
+enum { HACKY_DEVICE_LIST_SIZE = 6 };
static struct f_hidg *hacky_device_list[HACKY_DEVICE_LIST_SIZE];
static void hacky_device_list_add(struct f_hidg *hidg)
{
int i;
for (i = 0; i < HACKY_DEVICE_LIST_SIZE; i++) {
if (!hacky_device_list[i]) {
hacky_device_list[i] = hidg;
return;
}
}
diff --git a/drivers/usb/gadget/f_hid_android_u2f.c b/drivers/usb/gadget/f_hid_android_u2f.c
new file mode 100644
index 00000000000..950c244e032
--- /dev/null
+++ b/drivers/usb/gadget/f_hid_android_u2f.c
@@ -0,0 +1,28 @@
+#include <linux/platform_device.h>
+#include <linux/usb/g_hid.h>
+
+/* HID descriptor for a mouse */
+static struct hidg_func_descriptor ghid_device_android_u2f = {
+ .subclass = 0x01, /* Boot Interface Subclass */
+ .protocol = 0x00, /* Raw HID */
+ .report_length = 512,
+ .report_desc_length = 34,
+ .report_desc = {
+ 0x06, 0xD0, 0xF1, // Usage Page (FIDO Usage Page)
+ 0x09, 0x01, // Usage (FIDO Usage U2F HID)
+ 0xA1, 0x01, // Collection (Application)
+ 0x09, 0x20, // Usage (FIDO Usage Data In)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x40, // Report Count (HID Input Report Bytes)
+ 0x81, 0x02, // Input (HID Data & HID Absolute & HID Variable)
+ 0x09, 0x21, // Usage (FIDO Usage Data Out)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x40, // Report Count (HID Output Report Bytes)
+ 0x91, 0x02, // Output (HID Data & HID Absolute & HID Variable)
+ 0xC0 // End Collection
+ }
+};
```
* Find the Raspberry Pi's address by using the tool [`nmap`](https://nmap.org/), installable from your preferred package manager. * note that some of these settings are non-standard, and work around limitations experienced when debugging. See [warnings](#Warnings) below for more details.
`nmap -sn "10.42.0.*"` and look for the IP address which the host doesn't have (i.e. look for the ip address other than 10.42.0.1 in this example). 9. Recompile the kernel
* run `make -k$(nproc --all)` again.
* copy kernel to split_img subfolder inside AIK folder.
* rename the kernel image.
* run `./repackimg.sh` from AIK folder.
* reboot device to TWRP and copy `image-new.img` to device.
* install new image to `boot` partition, and reinstall Magisk.
* reboot device to check it still boots.
For example, when I ran the command, the output was <br />![10.42.0.172](Readme_Assets/Nmap_IP_scan.png) For Nexus 5X users, see the `precompiled` subdirectory for both the kernel, in the form of a precompiled `boot.img` and a precompiled form of this project. Using these, you may skip to [running the program](#Run-the-program).
so the IP address I am looking for is `10.42.0.172`.
9. Connecting
* Linux users
* Now finally we can SSH into the Raspberry Pi:
`ssh pi@RASPBERRY_PI_IP`
So in my example, the command would be:
`ssh pi@10.42.0.172`
* Other OSs
* For users of windows, see [ssh using PUTTY](https://desertbot.io/blog/headless-pi-zero-ssh-access-over-usb-windows#step-8-install-putty), and for users of OS X, you can simply ssh in using `ssh pi@raspberrypi.local` in a terminal.
* This should then ask you if you want to continue connecting, displaying the ECDSA key. Type `yes` and hit enter to continue.<br />![Continue connecting](Readme_Assets/SSH_connecting_ECDSA.png)
* Then, when the password is asked for, type `raspberry` - the default password in Raspbian.
10. Then, change your password using `passwd`. Enter `raspberry` as the current password, and a memorable password for the new password.
## Setting up USB config for multiple drivers simultaneously
Past this point, do not reboot / power off unless you wish to start all over again.
1. Edit `/boot/cmdline.txt` by using `sudoedit /boot/cmdline.txt`
* Remove the `modules-load=dwc2,g_ether` and ensure there is no trailing space.
* Close and save by pressing `ctrl-x`.
2. Edit `/etc/modules` by using `sudoedit /etc/modules`
* Add `libcomposite` at the end of the file (i.e. not on a line beggining with `#`).
## Setting up the libcomposite config script
### Getting Git
1. Update package list using `sudo apt-get update`
2. Get git by running `sudo apt-get install git`
### Using this respository
1. Grab the contents of this repository using `git clone https://github.com/Crystalix007/U2FDevice.git`
2. Enter the cloned directory by running `cd U2FDevice`
3. Install the config script using `sudo install -m755 Scripts/Kernel_HID_Config.sh /usr/bin/Kernel_HID_Config.sh`
4. Install the config script startup service by editing `/etc/rc.local`
* `sudoedit /etc/rc.local`
* Add `/usr/bin/Kernel_HID_Config.sh`, on a new line, before `'exit 0'`
5. At this point you are once again free to reboot / shutdown.
## Setting up udev rules ## Setting up udev rules
@@ -135,7 +234,12 @@ For Linux only.
On most distributions of Linux, devices get automatically managed based upon certain tags they expose to the computer. This program exposes custom tags to uniquely identify it from other U2F keys. However, as a result, the automatic rules do not include it in the list of USB devices to mount as U2F keys. On most distributions of Linux, devices get automatically managed based upon certain tags they expose to the computer. This program exposes custom tags to uniquely identify it from other U2F keys. However, as a result, the automatic rules do not include it in the list of USB devices to mount as U2F keys.
On your Linux desktop, run the command `ls /etc/udev/rules.d/`. Look for anything which seems related to U2F. <br />For example, on my computer, I have the rules `/etc/udev/rules.d/70-u2f.rules`. <br />Inside this file, the contents are: To find the required details of your Android device, run `lsusb`. Look for something named `Bus 0?? Device 0??: ID 18d1:4ee2 Google Inc. Nexus Device (debug)` or similar. Keep track of the value in your output like **`18d1:4ee2`**.
On your Linux desktop, run the command `ls /etc/udev/rules.d/`. Look for anything which seems related to U2F.
For example, on my computer, I have the rules `/etc/udev/rules.d/70-u2f.rules`.
Inside this file, the contents are:
``` ```
# Copyright (C) 2013-2015 Yubico AB # Copyright (C) 2013-2015 Yubico AB
@@ -195,12 +299,16 @@ KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="18d1", ATTRS{idProduct
LABEL="u2f_end" LABEL="u2f_end"
``` ```
Basically, this file contains the same contents as [Yubico's udev rules](https://github.com/Yubico/libu2f-host/blob/master/70-u2f.rules). <br /> Basically, this file contains the same contents as [Yubico's udev rules](https://github.com/Yubico/libu2f-host/blob/master/70-u2f.rules).
If you don't have any rules, download the [raw file](https://raw.githubusercontent.com/Yubico/libu2f-host/master/70-u2f.rules), and copy it to the `/etc/udev/rules.d/` directory. <br /> If you don't have any rules, download the [raw file](https://raw.githubusercontent.com/Yubico/libu2f-host/master/70-u2f.rules), and copy it to the `/etc/udev/rules.d/` directory.
Then, add: <br />
```# Rapsberry Pi U2F```<br /> ```KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="2900", TAG+="uaccess"``` <br />
on lines just before `LABEL="u2f_end"`.
Then, add:
```
# Android Device U2F
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="4ee2", TAG+="uaccess"
```
on lines just before `LABEL="u2f_end"`, replacing `18d1` and `4ee2` with their corresponding values from your previous `lsusb` output.
Then, reload the rules using `sudo udevadm control --reload-rules ` Then, reload the rules using `sudo udevadm control --reload-rules `
## Setting up to build ## Setting up to build
@@ -209,88 +317,107 @@ Then, reload the rules using `sudo udevadm control --reload-rules `
2. Copy the `cpp-base64` Makefile using `cp Scripts/cpp-base64-Makefile cpp-base64/Makefile` 2. Copy the `cpp-base64` Makefile using `cp Scripts/cpp-base64-Makefile cpp-base64/Makefile`
3. Copy the `micro-ecc` Makefile using `cp Scripts/uECC-Makefile micro-ecc/Makefile` 3. Copy the `micro-ecc` Makefile using `cp Scripts/uECC-Makefile micro-ecc/Makefile`
4. Make the object file directories using `mkdir obj && mkdir cpp-base64/obj && mkdir micro-ecc/obj` 4. Make the object file directories using `mkdir obj && mkdir cpp-base64/obj && mkdir micro-ecc/obj`
5. Grab the required library using `sudo apt-get install libmbedtls-dev` 5. Grab the required mbedtls library:
* outside this directory, run `git clone --recursive https://github.com/ARMmbed/mbedtls.git`
## Generate a certificate * enter the directory with `cd mbedtls`
* build for cross-compile with `sudo make DESTDIR=/opt
If you wish to do this automatically, just run `./GenCertificates.sh`, and answer the prompt with as much detail as you feel like entrusting to websites. CXX=<cross-compiler-prefix>-g++ CC=<cross-compiler-prefix>-gcc install`,
using the cross-compiler-prefix previously discussed in the [_Getting the
Alternatively, see `Readme.AttestationCertifcateGeneration.md` for a much more manual approach. kernel_](#Getting-the-kernel) phase.
## Build the program ## Build the program
1. Run `make` Run `make CROSS_COMPILE=<cross-compiler-prefix>`, using the cross-compiler-prefix previously discussed in the [_Getting the kernel_ phase](#Getting-the-kernel).
## Install the program ## Run the program
1. Run `sudo make install` 1. If you haven't already, enable [device USB debugging](https://developer.android.com/studio/debug/dev-options#enable).
2. Copy the program to the device to test with `adb push U2FDevice /data/local/tmp`
3. Open up a shell to your phone, using `adb shell`.
4. Navigate to where the file is stored, running `cd /data/local/tmp`.
5. Get SuperUser access, with `su`. Note, you may need to open the manager
program of your root toolkit of choice to permit root.
6. Run the program with `./U2FDevice`.
# Warnings # Warnings
## To shut down ## Logical issues
This device cannot be powered off without running a command in SSH (for now). If the device has its power interrupted by a sudden poweroff, it is likely there will be corruption which will render all data on the SD card useless. This project requests a 512-byte USB HID packet size. This contravenes the specification,
which states that 64 bytes should be used instead. This was chosen as, at least on the test
hardware, it was found that perfect multiples of the packet size caused packets to be
cached, and the device unable to read them, until four times the packet size was sent.
So, to power off currently, SSH into the device as shown above, then run the command `sudo poweroff ; exit` As a result, by maximising the packet size, unless a message of 512, 1024, 1536, ...
bytes needs to be sent, this caching issue will not arise. This obviously has a reduced
probability of occurring than with a 64-byte packet size. Additionally, 512 bytes is the
maximum limit of packet size on USB Version 2.0. Therefore, it cannot be made any larger.
If this issue does not arise with your hardware (i.e., you change `.report_length = 512`
in `drivers/usb/gadget/f_hid_android_u2f.c` to `.report_length = 64`, and U2F still works
after you reinstall the kernel), this is probably even better, and more compatible with
client U2F libraries.
## Security issues ## Security issues
This project is intended solely for the use in experimentation of the use of U2F or as a backup for keys. It is _not_ intended for use as a regular day-to-day key for several reasons. This project is intended solely for the use in experimentation of the use of
U2F or as a backup for keys. It is _not_ intended for use as a regular
day-to-day key for several reasons.
* Private keys are stored in a freely accessible file (to users of the pi) `/usr/share/U2FDevice/U2F_Priv_Keys.txt` * Private keys are stored in a freely accessible file (to all apps on the device) `/sdcard/U2F/U2F_Priv_Keys.txt`
* This program doesn't comply with the specification with regards to user interaction. There is a specific code sent to check for user interaction for registering or authenticating keys. This requirement is ignored by this implementation as there are no pre-existing buttons on the Pi. * This program doesn't comply with the specification with regards to user
* Whilst this program is functional, it has the possibility of unintended crashes. I have tested to the limits I require, but you may require additional assurance. interaction. There is a specific code sent to check for user interaction for
* This program's private keys are stored on an SD card. This is an incredibly volatile medium (and yes, regularly I mean that in the computing sense - SD cards regularly do cleanup / maintenance routines that can cause complete corruption if the power is lost suddenly). I would not consider these keys safe under very regular use (infrequent use should be fine though). registering or authenticating keys. This requirement is ignored by this
* This solution is rather unwieldy - it requires a long boot time and must be shutdown (for now) with a command using SSH. implementation as currently, the program cannot natively interface with the
Android device's buttons.
* Whilst this program is functional, it has the possibility of unintended
crashes. I have tested to the limits I require, but you may require
additional assurance.
* This solution is rather unwieldy - it requires a modified kernel and must be
launched from the commandline.
For these reasons, if you want to use this as a way to backup your other U2F devices against loss, this may be a very valid solution, but please don't rely solely on this solution for U2F security. For these reasons, if you want to use this as a way to backup your other U2F
devices against loss, this may be a very valid solution, but please don't rely
solely on this solution for U2F security.
## To improve RNG (improve crypto security) ## To change the Attestation certificate
1. Install `rng-tools` with `sudo apt-get install rng-tools` This is probably highly advisable.
All registration requests use this private key, so you can be uniquely
identified by having a unique attestation certificate, however, it permits you
to have a secure attestation certificate, so still worth it.
## Notes about a custom attestation certificate See the [`Readme.AttestationCertificateGeneration.txt`](Readme.AttestationCertificateGeneration.txt)
By using a custom attestation certificate, you lose the anonymity of conventional u2f keys. This is because they are produced in large batches and thus can share a single certificate, burned into some private ROM. However, since you require the private key to sign, and this repo is public, it is impossible to use a single signature for everyone who uses this repository.
However, by generating your own certificate, you can be more assured about the inherent security of your certificate (no-one can leak the private key but you).
Note, however, that this key and certificate is only used for registration - not for further authentication.
See the `Readme.AttestationCertificateGeneration.md`
# Running the program # Running the program
## To run At this point, the program should be tested using U2F demo websites. For
example, [Yubico's U2F demo](https://demo.yubico.com/u2f?tab=register),
Run `sudo systemctl start U2FDevice.service` [Yubico's WebAuthn demo](https://demo.yubico.com/webauthn-technical/registration),
or [DUO Lab's WebAuthn demo](https://webauthn.io/). First register the device, then test authentication.
At this point, the program should be tested using U2F demo websites. For example, [Yubico's U2F demo](https://demo.yubico.com/u2f?tab=register), or [appspot's U2F demo](https://crxjs-dot-u2fdemo.appspot.com/). First register the device, then test authentication.
If the program doesn't work on these - don't use as a backup device. If the program doesn't work on these - don't use as a backup device.
## To run automatically at boot
Once the program runs successfully, you can enable automatic startup at boot.
Run `sudo systemctl enable U2FDevice.service`
## Debug files ## Debug files
For those of you wishing to dig around in the actual protocol work, these are the files used by the application to log the activity. For those of you wishing to dig around in the actual protocol work, these are the files used by the application to log the activity. For this, you must enable the `DEBUG_STREAMS` flag in `Architecture.hpp`, and recompile.
The documents used for raw communication contain just that - the raw data sent to and from the device. <br />To view this data, I would recommend using `od` . For example, `cat /tmp/comdev.txt | od -tx1 -Anone -v` in order to print out the bytes sent from the Raspberry Pi to the PC in hexadecimal form. The documents used for raw communication contain just that - the raw data sent to and from the device.
To view this data, I would recommend using `od` or `xxd`. For example, `cat comdev.txt | od -tx1 -Anone -v` in order to print out the bytes sent from the Android device to the PC in hexadecimal form.
The documents used for packets show the higher-level structures used in the U2F protocol. The first level above the data sent in USB frames is the `U2F-HID` protocol. The most recent specification for these packets is available [here](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-hid-protocol-v1.2-ps-20170411.html) (as of 12/07/2018). This level details mainly how the PC performs the setup for talking to the device. The documents used for packets show the higher-level structures used in the U2F protocol. The first level above the data sent in USB frames is the `U2F-HID` protocol. The most recent specification for these packets is available [here](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-hid-protocol-v1.2-ps-20170411.html) (as of 12/07/2018). This level details mainly how the PC performs the setup for talking to the device.
The next level above that is the actual `U2F` protocol. The most recent specification for these messages is available [here](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html) (as of 12/07/2018). This level details how registration and authentication actually occurs. The next level above that is the actual `U2F` protocol. The most recent specification for these messages is available [here](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html) (as of 12/07/2018). This level details how registration and authentication actually occurs.
These file paths assume that the U2FDevice program is being run from `/data/local/tmp/`, as `./U2FDevice`.
* __Raw communication__ * __Raw communication__
* /tmp/comdev.txt * /data/local/tmp/comdev.txt
* /tmp/comhost.txt * /data/local/tmp/comhost.txt
* __Packets__ * __Packets__
* /tmp/devPackets.html * /data/local/tmp/devPackets.html
* /tmp/devAPDU.html * /data/local/tmp/devAPDU.html
* /tmp/hostPackets.html * /data/local/tmp/hostPackets.html
* /tmp/hostAPDU.html * /data/local/tmp/hostAPDU.html

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_C_FLAGS, "${CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -Werror")
add_library(cppb64 STATIC base64.cpp)

137
Scripts/u2f-kernel.patch Normal file
View File

@@ -0,0 +1,137 @@
diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c
index ced63c82ae7..41ac7ac18ca 100644
--- a/drivers/usb/gadget/android.c
+++ b/drivers/usb/gadget/android.c
@@ -55,20 +55,21 @@
#include "u_ctrl_hsic.c"
#include "u_data_hsic.c"
#include "u_ctrl_hsuart.c"
#include "u_data_hsuart.c"
#include "f_ccid.c"
#include "f_mtp.c"
#include "f_accessory.c"
#include "f_hid.h"
#include "f_hid_android_keyboard.c"
#include "f_hid_android_mouse.c"
+#include "f_hid_android_u2f.c"
#include "f_rndis.c"
#include "rndis.c"
#include "f_qc_ecm.c"
#include "f_mbim.c"
#include "f_qc_rndis.c"
#include "u_data_ipa.c"
#include "u_bam_data.c"
#include "f_ecm.c"
#include "u_ether.c"
#include "u_qc_ether.c"
@@ -2855,21 +2856,21 @@ static int uasp_function_bind_config(struct android_usb_function *f,
static struct android_usb_function uasp_function = {
.name = "uasp",
.init = uasp_function_init,
.cleanup = uasp_function_cleanup,
.bind_config = uasp_function_bind_config,
};
static int hid_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev)
{
- return ghid_setup(cdev->gadget, 2);
+ return ghid_setup(cdev->gadget, 3);
}
static void hid_function_cleanup(struct android_usb_function *f)
{
ghid_cleanup();
}
static int hid_function_bind_config(struct android_usb_function *f, struct usb_configuration *c)
{
int ret;
@@ -2878,20 +2879,26 @@ static int hid_function_bind_config(struct android_usb_function *f, struct usb_c
if (ret) {
pr_info("%s: hid_function_bind_config keyboard failed: %d\n", __func__, ret);
return ret;
}
printk(KERN_INFO "hid mouse\n");
ret = hidg_bind_config(c, &ghid_device_android_mouse, 1);
if (ret) {
pr_info("%s: hid_function_bind_config mouse failed: %d\n", __func__, ret);
return ret;
}
+ printk(KERN_INFO "hid u2f\n");
+ ret = hidg_bind_config(c, &ghid_device_android_u2f, 2);
+ if (ret) {
+ pr_info("%s: hid_function_bind_config u2f failed: %d\n", __func__, ret);
+ return ret;
+ }
return 0;
}
static struct android_usb_function hid_function = {
.name = "hid",
.init = hid_function_init,
.cleanup = hid_function_cleanup,
.bind_config = hid_function_bind_config,
};
diff --git a/drivers/usb/gadget/f_hid.c b/drivers/usb/gadget/f_hid.c
index 155d9fb3a08..117bc00feac 100644
--- a/drivers/usb/gadget/f_hid.c
+++ b/drivers/usb/gadget/f_hid.c
@@ -57,21 +57,21 @@ struct f_hidg {
int minor;
struct cdev cdev;
struct usb_function func;
struct usb_ep *in_ep;
struct usb_ep *out_ep;
};
/* Hacky device list to fix f_hidg_write being called after device destroyed.
It covers only most common race conditions, there will be rare crashes anyway. */
-enum { HACKY_DEVICE_LIST_SIZE = 4 };
+enum { HACKY_DEVICE_LIST_SIZE = 6 };
static struct f_hidg *hacky_device_list[HACKY_DEVICE_LIST_SIZE];
static void hacky_device_list_add(struct f_hidg *hidg)
{
int i;
for (i = 0; i < HACKY_DEVICE_LIST_SIZE; i++) {
if (!hacky_device_list[i]) {
hacky_device_list[i] = hidg;
return;
}
}
diff --git a/drivers/usb/gadget/f_hid_android_u2f.c b/drivers/usb/gadget/f_hid_android_u2f.c
new file mode 100644
index 00000000000..950c244e032
--- /dev/null
+++ b/drivers/usb/gadget/f_hid_android_u2f.c
@@ -0,0 +1,28 @@
+#include <linux/platform_device.h>
+#include <linux/usb/g_hid.h>
+
+/* HID descriptor for a mouse */
+static struct hidg_func_descriptor ghid_device_android_u2f = {
+ .subclass = 0x01, /* Boot Interface Subclass */
+ .protocol = 0x00, /* Raw HID */
+ .report_length = 512,
+ .report_desc_length = 34,
+ .report_desc = {
+ 0x06, 0xD0, 0xF1, // Usage Page (FIDO Usage Page)
+ 0x09, 0x01, // Usage (FIDO Usage U2F HID)
+ 0xA1, 0x01, // Collection (Application)
+ 0x09, 0x20, // Usage (FIDO Usage Data In)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x40, // Report Count (HID Input Report Bytes)
+ 0x81, 0x02, // Input (HID Data & HID Absolute & HID Variable)
+ 0x09, 0x21, // Usage (FIDO Usage Data Out)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x40, // Report Count (HID Output Report Bytes)
+ 0x91, 0x02, // Output (HID Data & HID Absolute & HID Variable)
+ 0xC0 // End Collection
+ }
+};

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_C_FLAGS, "${CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -Werror")
add_library(uECC STATIC uECC.c)

View File

@@ -33,10 +33,15 @@ std::map<Storage::KeyHandle, Storage::KeyCount> Storage::keyCounts{};
void Storage::init(const string& dirPrefix) { void Storage::init(const string& dirPrefix) {
Storage::filename = dirPrefix + "U2F_Priv_Keys.txt"; Storage::filename = dirPrefix + "U2F_Priv_Keys.txt";
ifstream file{ Storage::filename }; ifstream file{ Storage::filename };
init(file);
}
void Storage::init(std::istream& inputStream) {
string line; string line;
size_t lineNumber = 0; size_t lineNumber = 0;
while (getline(file, line)) { while (getline(inputStream, line)) {
auto strLineNum = to_string(lineNumber); auto strLineNum = to_string(lineNumber);
stringstream ss{ line }; stringstream ss{ line };
string keyHStr, appStr, privStr, pubStr, keyCStr; string keyHStr, appStr, privStr, pubStr, keyCStr;
@@ -79,29 +84,32 @@ void Storage::init(const string& dirPrefix) {
void Storage::save() { void Storage::save() {
ofstream file{ Storage::filename }; ofstream file{ Storage::filename };
Storage::save(file);
}
for (const auto& keypair : Storage::appParams) { void Storage::save(ostream& outputStream) {
for (auto& keypair : Storage::appParams) {
const auto& keyID = keypair.first; const auto& keyID = keypair.first;
const auto& appParam = keypair.second; const auto& appParam = keypair.second;
const auto& privKey = Storage::privKeys[keyID]; const auto& privKey = Storage::privKeys[keyID];
const auto& pubKey = Storage::pubKeys[keyID]; const auto& pubKey = Storage::pubKeys[keyID];
const auto& keyCount = Storage::keyCounts[keyID]; const auto& keyCount = Storage::keyCounts[keyID];
file << keyID; outputStream << keyID;
file << ' '; outputStream << ' ';
string appPStr{}; string appPStr{};
b64encode(appParam, appPStr); b64encode(appParam, appPStr);
file << appPStr << ' '; outputStream << appPStr << ' ';
string privKStr{}; string privKStr{};
b64encode(privKey, privKStr); b64encode(privKey, privKStr);
file << privKStr << ' '; outputStream << privKStr << ' ';
string pubKStr{}; string pubKStr{};
b64encode(pubKey, pubKStr); b64encode(pubKey, pubKStr);
file << pubKStr << ' '; outputStream << pubKStr << ' ';
file << keyCount << endl; outputStream << keyCount << endl;
} }
} }

View File

@@ -37,7 +37,9 @@ protected:
public: public:
static void init(const std::string& dirPrefix = ""); static void init(const std::string& dirPrefix = "");
static void init(std::istream& inputStream);
static void save(); static void save();
static void save(std::ostream& outputStream);
static std::map<KeyHandle, AppParam> appParams; static std::map<KeyHandle, AppParam> appParams;
static std::map<KeyHandle, PrivKey> privKeys; static std::map<KeyHandle, PrivKey> privKeys;
static std::map<KeyHandle, PubKey> pubKeys; static std::map<KeyHandle, PubKey> pubKeys;

View File

@@ -17,109 +17,139 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "Streams.hpp" #include "Streams.hpp"
#include "Architecture.hpp"
#include "IO.hpp"
#include <android/log.h>
#include <cstdio> #include <cstdio>
#include <fcntl.h> #include <fcntl.h>
#include <iostream> #include <iostream>
#include <sys/socket.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/un.h>
#include <unistd.h> #include <unistd.h>
using namespace std; using std::cerr;
using std::clog;
using std::endl;
using std::runtime_error;
using std::shared_ptr;
using std::string;
shared_ptr<int> genHostDescriptor();
#ifdef DEBUG_STREAMS #ifdef DEBUG_STREAMS
FILE* initHTML(FILE* fPtr, const string& title); FILE* initHTML(FILE* fPtr, const string& title);
void closeHTML(FILE* fPtr); void closeHTML(FILE* fPtr);
shared_ptr<FILE> genComHostStream();
shared_ptr<FILE> genHostPacketStream();
shared_ptr<FILE> genHostAPDUStream();
shared_ptr<FILE> genComDevStream();
shared_ptr<FILE> genDevPacketStream();
shared_ptr<FILE> genDevAPDUStream();
#endif #endif
shared_ptr<int> getHostDescriptor() { shared_ptr<int>& getHostDescriptor() {
static shared_ptr<int> descriptor{ new int{ open(HID_DEV, O_RDWR | O_NONBLOCK | O_APPEND) }, static shared_ptr<int> descriptor{
[](int* fd) { #ifndef MANUAL_LIFETIME
close(*fd); genHostDescriptor()
delete fd; #endif
} }; };
if (*descriptor == -1)
throw runtime_error{ "Descriptor is unavailable" };
return descriptor; return descriptor;
} }
#ifdef DEBUG_STREAMS #ifdef DEBUG_STREAMS
shared_ptr<FILE> getComHostStream() {
static shared_ptr<FILE> stream{ fopen(DEBUG_STREAMS "comhost.txt", "wb"), [](FILE* f) {
clog << "Closing comhost stream" << endl;
fclose(f);
} };
shared_ptr<FILE>& getComHostStream() {
static shared_ptr<FILE> stream{
#ifndef MANUAL_LIFETIME
genComHostStream()
#endif
};
#ifndef MANUAL_LIFETIME
if (!stream) if (!stream)
clog << "Stream is unavailable" << endl; clog << "Stream is unavailable" << endl;
#endif
return stream; return stream;
} }
shared_ptr<FILE> getHostPacketStream() { shared_ptr<FILE>& getHostPacketStream() {
static shared_ptr<FILE> stream{ initHTML(fopen(DEBUG_STREAMS "hostpackets.html", "wb"), static shared_ptr<FILE> stream{
"Host Packets"), #ifndef MANUAL_LIFETIME
[](FILE* f) { genHostPacketStream()
clog << "Closing hostPackets stream" << endl; #endif
closeHTML(f); };
} };
#ifndef MANUAL_LIFETIME
if (!stream) if (!stream)
clog << "Stream is unavailable" << endl; clog << "Stream is unavailable" << endl;
#endif
return stream; return stream;
} }
shared_ptr<FILE> getHostAPDUStream() { shared_ptr<FILE>& getHostAPDUStream() {
static shared_ptr<FILE> stream{ initHTML(fopen(DEBUG_STREAMS "hostAPDU.html", "wb"), static shared_ptr<FILE> stream{
"Host APDU"), #ifndef MANUAL_LIFETIME
[](FILE* f) { genHostAPDUStream()
clog << "Closing host APDU stream" << endl; #endif
closeHTML(f); };
} };
#ifndef MANUAL_LIFETIME
if (!stream) if (!stream)
clog << "Stream is unavailable" << endl; clog << "Stream is unavailable" << endl;
#endif
return stream; return stream;
} }
shared_ptr<FILE> getComDevStream() { shared_ptr<FILE>& getComDevStream() {
static shared_ptr<FILE> stream{ fopen(DEBUG_STREAMS "comdev.txt", "wb"), [](FILE* f) { static shared_ptr<FILE> stream{
clog << "Closing comdev stream" << endl; #ifndef MANUAL_LIFETIME
fclose(f); genComDevStream()
} }; #endif
};
#ifndef MANUAL_LIFETIME
if (!stream) if (!stream)
clog << "Stream is unavailable" << endl; clog << "Stream is unavailable" << endl;
#endif
return stream; return stream;
} }
shared_ptr<FILE> getDevPacketStream() { shared_ptr<FILE>& getDevPacketStream() {
static shared_ptr<FILE> stream{ initHTML(fopen(DEBUG_STREAMS "devpackets.html", "wb"), static shared_ptr<FILE> stream{
"Dev Packets"), #ifndef MANUAL_LIFETIME
[](FILE* f) { genDevPacketStream()
clog << "Closing devPackets stream" << endl; #endif
closeHTML(f); };
} };
#ifndef MANUAL_LIFETIME
if (!stream) if (!stream)
clog << "Stream is unavailable" << endl; clog << "Stream is unavailable" << endl;
#endif
return stream; return stream;
} }
shared_ptr<FILE> getDevAPDUStream() { shared_ptr<FILE>& getDevAPDUStream() {
static shared_ptr<FILE> stream{ initHTML(fopen(DEBUG_STREAMS "devAPDU.html", "wb"), "Dev APDU"), static shared_ptr<FILE> stream{
[](FILE* f) { #ifndef MANUAL_LIFETIME
clog << "Closing dev APDU stream" << endl; genDevAPDUStream()
closeHTML(f); #endif
} }; };
#ifndef MANUAL_LIFETIME
if (!stream) if (!stream)
clog << "Stream is unavailable" << endl; clog << "Stream is unavailable" << endl;
#endif
return stream; return stream;
} }
@@ -177,4 +207,137 @@ void closeHTML(FILE* fPtr) {
if (successCode != 0) if (successCode != 0)
cerr << "File closing error: " << errno << endl; cerr << "File closing error: " << errno << endl;
} }
#endif
shared_ptr<int> genHostDescriptor() {
int descriptor;
#ifdef HID_SOCKET
if (access(clientSocket.c_str(), F_OK)) {
remove(clientSocket.c_str());
}
descriptor = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
if (descriptor == -1)
throw runtime_error{ "Unable to open client socket" };
sockaddr_un serverSockAddr{}, clientSockAddr{};
clientSockAddr.sun_family = AF_UNIX;
strncpy(clientSockAddr.sun_path, clientSocket.c_str(), sizeof(clientSockAddr.sun_path) - 1);
// Attempt to remove existing
unlink(clientSocket.c_str());
int result = ::bind(descriptor, (sockaddr*)&clientSockAddr, sizeof(clientSockAddr));
if (result == -1)
throw runtime_error{ "Unable to bind to client socket: " + clientSocket };
serverSockAddr.sun_family = AF_UNIX;
strncpy(serverSockAddr.sun_path, HID_DEV, sizeof(serverSockAddr.sun_path) - 1);
for (size_t connectCount = 0; connectCount < 100; connectCount++) {
result = connect(descriptor, (sockaddr*)&serverSockAddr, sizeof(serverSockAddr));
if (result != -1)
break;
usleep(100'000);
}
if (result == -1)
throw runtime_error{ "Unable to connect to server socket: " + string{ HID_DEV } };
__android_log_print(ANDROID_LOG_DEBUG, "U2FDevice", "Connected to server");
return shared_ptr<int>{ new int{ descriptor }, [](const int* fd) {
close(*fd);
remove(clientSocket.c_str());
delete fd;
} };
#else
descriptor = open(HID_DEV, O_RDWR | O_NONBLOCK | O_APPEND);
if (descriptor == -1)
throw runtime_error{ "Descriptor is unavailable" };
return shared_ptr<int>{ new int{ descriptor }, [](const int* fd) {
close(*fd);
delete fd;
} };
#endif
}
shared_ptr<FILE> genComHostStream() {
return shared_ptr<FILE>{ fopen((cacheDirectory + "/comhost.txt").c_str(), "wb"),
[](FILE* f) {
clog << "Closing comhost stream" << endl;
fclose(f);
} };
}
shared_ptr<FILE> genHostPacketStream() {
return shared_ptr<FILE>{ initHTML(fopen((cacheDirectory + "/hostpackets.html").c_str(), "wb"), "Host Packets"),
[](FILE* f) {
clog << "Closing hostPackets stream" << endl;
closeHTML(f);
} };
}
shared_ptr<FILE> genHostAPDUStream() {
return shared_ptr<FILE>{ initHTML(fopen((cacheDirectory + "/hostAPDU.html").c_str(), "wb"), "Host APDU"),
[](FILE* f) {
clog << "Closing host APDU stream" << endl;
closeHTML(f);
} };
}
shared_ptr<FILE> genComDevStream() {
return shared_ptr<FILE>{ fopen((cacheDirectory + "/comdev.txt").c_str(), "wb"),
[](FILE* f) {
clog << "Closing comdev stream" << endl;
fclose(f);
} };
}
shared_ptr<FILE> genDevPacketStream() {
return shared_ptr<FILE>{ initHTML(fopen((cacheDirectory + "/devpackets.html").c_str(), "wb"), "Dev Packets"),
[](FILE* f) {
clog << "Closing devPackets stream" << endl;
closeHTML(f);
} };
}
shared_ptr<FILE> genDevAPDUStream() {
return shared_ptr<FILE>{ initHTML(fopen((cacheDirectory + "/devAPDU.html").c_str(), "wb"),
"Dev APDU"),
[](FILE* f) {
clog << "Closing dev APDU stream" << endl;
closeHTML(f);
} };
}
#ifdef MANUAL_LIFETIME
void initStreams() {
getHostDescriptor() = genHostDescriptor();
getComHostStream() = genComHostStream();
getHostPacketStream() = genHostPacketStream();
getHostAPDUStream() = genHostAPDUStream();
getComDevStream() = genComDevStream();
getDevPacketStream() = genDevPacketStream();
getDevAPDUStream() = genDevAPDUStream();
}
void closeStreams() {
getHostDescriptor().reset();
getComHostStream().reset();
getHostPacketStream().reset();
getHostAPDUStream().reset();
getComDevStream().reset();
getDevPacketStream().reset();
getDevAPDUStream().reset();
}
#endif #endif

View File

@@ -21,13 +21,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cstdio> #include <cstdio>
#include <memory> #include <memory>
std::shared_ptr<int> getHostDescriptor(); std::shared_ptr<int>& getHostDescriptor();
#ifdef MANUAL_LIFETIME
void initStreams();
void closeStreams();
#endif
#ifdef DEBUG_STREAMS #ifdef DEBUG_STREAMS
std::shared_ptr<FILE> getComHostStream(); std::shared_ptr<FILE>& getComHostStream();
std::shared_ptr<FILE> getHostPacketStream(); std::shared_ptr<FILE>& getHostPacketStream();
std::shared_ptr<FILE> getHostAPDUStream(); std::shared_ptr<FILE>& getHostAPDUStream();
std::shared_ptr<FILE> getComDevStream(); std::shared_ptr<FILE>& getComDevStream();
std::shared_ptr<FILE> getDevPacketStream(); std::shared_ptr<FILE>& getDevPacketStream();
std::shared_ptr<FILE> getDevAPDUStream(); std::shared_ptr<FILE>& getDevAPDUStream();
#endif #endif

View File

@@ -20,8 +20,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "Controller.hpp" #include "Controller.hpp"
#include "LED.hpp" #include "LED.hpp"
#include "Storage.hpp" #include "Storage.hpp"
#include <csignal>
#include <iostream> #include <iostream>
#include <signal.h>
#include <unistd.h> #include <unistd.h>
using namespace std; using namespace std;
@@ -37,9 +37,6 @@ bool initialiseLights(const string& prog) {
} catch (runtime_error& e) { } catch (runtime_error& e) {
cerr << e.what() << endl; cerr << e.what() << endl;
if (getuid() != 0)
cerr << "Try running as root, using \'sudo " << prog << "\'" << endl;
return false; return false;
} }
@@ -58,9 +55,6 @@ int handleTransactions(const string& prog, const string& privKeyDir) {
} catch (const runtime_error& e) { } catch (const runtime_error& e) {
cerr << e.what() << endl; cerr << e.what() << endl;
if (getuid() != 0)
cerr << "Try running as root, using \'sudo " << prog << "\'" << endl;
raise(SIGINT); raise(SIGINT);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@@ -78,9 +72,6 @@ bool deinitialiseLights(const string& prog) {
} catch (runtime_error& e) { } catch (runtime_error& e) {
cerr << e.what() << endl; cerr << e.what() << endl;
if (getuid() != 0)
cerr << "Try running as root, using \'sudo " << prog << "\'" << endl;
return false; return false;
} }

View File

@@ -22,6 +22,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <vector> #include <vector>
struct U2FMessage { struct U2FMessage {
public:
uint32_t cid; uint32_t cid;
uint8_t cmd; uint8_t cmd;
std::vector<uint8_t> data; std::vector<uint8_t> data;

View File

@@ -47,7 +47,7 @@ U2F_Authenticate_APDU::U2F_Authenticate_APDU(const U2F_Msg_CMD& msg, const vecto
copy(data.begin() + 65, data.begin() + 65 + keyHLen, back_inserter(keyH)); copy(data.begin() + 65, data.begin() + 65 + keyHLen, back_inserter(keyH));
} }
void U2F_Authenticate_APDU::respond(const uint32_t channelID) const { void U2F_Authenticate_APDU::respond(const uint32_t channelID, bool hasAuthorisation) const {
if (keyH.size() != sizeof(Storage::KeyHandle)) { if (keyH.size() != sizeof(Storage::KeyHandle)) {
// Respond with error code - key handle is of wrong size // Respond with error code - key handle is of wrong size
cerr << "Invalid key handle length" << endl; cerr << "Invalid key handle length" << endl;
@@ -71,6 +71,8 @@ void U2F_Authenticate_APDU::respond(const uint32_t channelID) const {
return; return;
} }
uint8_t presence;
switch (p1) { switch (p1) {
case ControlCode::CheckOnly: case ControlCode::CheckOnly:
this->error(channelID, APDU_STATUS::SW_CONDITIONS_NOT_SATISFIED); this->error(channelID, APDU_STATUS::SW_CONDITIONS_NOT_SATISFIED);
@@ -98,7 +100,7 @@ void U2F_Authenticate_APDU::respond(const uint32_t channelID) const {
auto& keyCount = Storage::keyCounts[keyHB]; auto& keyCount = Storage::keyCounts[keyHB];
keyCount++; keyCount++;
response.push_back(0x01); response.push_back(hasAuthorisation ? 1u : 0u);
response.insert(response.end(), FIELD_BE(keyCount)); response.insert(response.end(), FIELD_BE(keyCount));
Digest digest; Digest digest;
@@ -110,7 +112,7 @@ void U2F_Authenticate_APDU::respond(const uint32_t channelID) const {
mbedtls_sha256_update(&shaContext, reinterpret_cast<const uint8_t*>(appParam.data()), mbedtls_sha256_update(&shaContext, reinterpret_cast<const uint8_t*>(appParam.data()),
sizeof(appParam)); sizeof(appParam));
uint8_t userPresence{ 1u }; uint8_t userPresence = hasAuthorisation ? 1u : 0u;
mbedtls_sha256_update(&shaContext, &userPresence, 1); mbedtls_sha256_update(&shaContext, &userPresence, 1);
const auto beCounter = beEncode(keyCount); const auto beCounter = beEncode(keyCount);
mbedtls_sha256_update(&shaContext, beCounter.data(), beCounter.size()); mbedtls_sha256_update(&shaContext, beCounter.data(), beCounter.size());
@@ -128,3 +130,7 @@ void U2F_Authenticate_APDU::respond(const uint32_t channelID) const {
msg.write(); msg.write();
} }
bool U2F_Authenticate_APDU::requiresAuthorisation() const {
return p1 == ControlCode::EnforcePresenceSign;
}

View File

@@ -20,8 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "Storage.hpp" #include "Storage.hpp"
#include "U2F_Msg_CMD.hpp" #include "U2F_Msg_CMD.hpp"
class U2F_Authenticate_APDU : public U2F_Msg_CMD { struct U2F_Authenticate_APDU : U2F_Msg_CMD {
protected:
uint8_t controlByte; uint8_t controlByte;
std::array<uint8_t, 32> challengeP; std::array<uint8_t, 32> challengeP;
Storage::AppParam appParam; Storage::AppParam appParam;
@@ -30,9 +29,9 @@ protected:
public: public:
U2F_Authenticate_APDU(const U2F_Msg_CMD& msg, const std::vector<uint8_t>& data); U2F_Authenticate_APDU(const U2F_Msg_CMD& msg, const std::vector<uint8_t>& data);
virtual void respond(const uint32_t channelID) const override; bool requiresAuthorisation() const override;
virtual void respond(const uint32_t channelID, bool hasAuthorisation) const override;
protected:
enum ControlCode { enum ControlCode {
CheckOnly = 0x07, CheckOnly = 0x07,
EnforcePresenceSign = 0x03, EnforcePresenceSign = 0x03,

View File

@@ -24,9 +24,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
using namespace std; using namespace std;
shared_ptr<U2F_CMD> U2F_CMD::get(const shared_ptr<U2FMessage> uMsg) { shared_ptr<U2F_CMD> U2F_CMD::get(const U2FMessage& uMsg) {
try { try {
switch (uMsg->cmd) { switch (uMsg.cmd) {
case U2FHID_PING: case U2FHID_PING:
return make_shared<U2F_Ping_CMD>(uMsg); return make_shared<U2F_Ping_CMD>(uMsg);
case U2FHID_MSG: case U2FHID_MSG:
@@ -34,11 +34,15 @@ shared_ptr<U2F_CMD> U2F_CMD::get(const shared_ptr<U2FMessage> uMsg) {
case U2FHID_INIT: case U2FHID_INIT:
return make_shared<U2F_Init_CMD>(uMsg); return make_shared<U2F_Init_CMD>(uMsg);
default: default:
U2FMessage::error(uMsg->cid, ERR_INVALID_CMD); U2FMessage::error(uMsg.cid, ERR_INVALID_CMD);
return {}; return {};
} }
} catch (runtime_error& ignored) { } catch (runtime_error& ignored) {
U2FMessage::error(uMsg->cid, ERR_OTHER); U2FMessage::error(uMsg.cid, ERR_OTHER);
return {}; return {};
} }
} }
bool U2F_CMD::requiresAuthorisation() const {
return false;
}

View File

@@ -20,13 +20,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "U2FMessage.hpp" #include "U2FMessage.hpp"
#include <memory> #include <memory>
class U2F_CMD { struct U2F_CMD {
protected: protected:
U2F_CMD() = default; U2F_CMD() = default;
public: public:
virtual ~U2F_CMD() = default; virtual ~U2F_CMD() = default;
static std::shared_ptr<U2F_CMD> get(const std::shared_ptr<U2FMessage> uMsg); virtual bool requiresAuthorisation() const;
virtual void respond(const uint32_t channelID) const = 0; static std::shared_ptr<U2F_CMD> get(const U2FMessage& uMsg);
virtual bool modifiesPersistentState() const = 0; virtual void respond(const uint32_t channelID, bool hasAuthorisation) const = 0;
}; // For polymorphic type casting }; // For polymorphic type casting

View File

@@ -23,21 +23,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
using namespace std; using namespace std;
U2F_Init_CMD::U2F_Init_CMD(const shared_ptr<U2FMessage> uMsg) { U2F_Init_CMD::U2F_Init_CMD(const U2FMessage& uMsg) {
if (uMsg->cmd != U2FHID_INIT) if (uMsg.cmd != U2FHID_INIT)
throw runtime_error{ "Failed to get U2F Init message" }; throw runtime_error{ "Failed to get U2F Init message" };
else if (uMsg->cid != CID_BROADCAST) { else if (uMsg.cid != CID_BROADCAST) {
U2FMessage::error(uMsg->cid, ERR_OTHER); U2FMessage::error(uMsg.cid, ERR_OTHER);
throw runtime_error{ "Invalid CID for init command" }; throw runtime_error{ "Invalid CID for init command" };
} else if (uMsg->data.size() != INIT_NONCE_SIZE) { } else if (uMsg.data.size() != INIT_NONCE_SIZE) {
U2FMessage::error(uMsg->cid, ERR_INVALID_LEN); U2FMessage::error(uMsg.cid, ERR_INVALID_LEN);
throw runtime_error{ "Init nonce is incorrect size" }; throw runtime_error{ "Init nonce is incorrect size" };
} }
this->nonce = *reinterpret_cast<const uint64_t*>(uMsg->data.data()); this->nonce = *reinterpret_cast<const uint64_t*>(uMsg.data.data());
} }
void U2F_Init_CMD::respond(const uint32_t channelID) const { void U2F_Init_CMD::respond(const uint32_t channelID, bool) const {
U2FMessage msg{}; U2FMessage msg{};
msg.cid = CID_BROADCAST; msg.cid = CID_BROADCAST;
msg.cmd = U2FHID_INIT; msg.cmd = U2FHID_INIT;
@@ -52,7 +52,3 @@ void U2F_Init_CMD::respond(const uint32_t channelID) const {
msg.write(); msg.write();
} }
bool U2F_Init_CMD::modifiesPersistentState() const {
return false;
}

View File

@@ -22,12 +22,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
class U2F_Init_CMD : public U2F_CMD { struct U2F_Init_CMD : U2F_CMD {
protected:
uint64_t nonce; uint64_t nonce;
public: public:
U2F_Init_CMD(const std::shared_ptr<U2FMessage> uMsg); U2F_Init_CMD(const U2FMessage& uMsg);
virtual void respond(const uint32_t channelID) const override; virtual void respond(const uint32_t channelID, bool) const override;
virtual bool modifiesPersistentState() const override;
}; };

View File

@@ -55,21 +55,21 @@ uint32_t U2F_Msg_CMD::getLe(const uint32_t byteCount, vector<uint8_t> bytes) {
return 0; return 0;
} }
shared_ptr<U2F_Msg_CMD> U2F_Msg_CMD::generate(const shared_ptr<U2FMessage> uMsg) { shared_ptr<U2F_Msg_CMD> U2F_Msg_CMD::generate(const U2FMessage& uMsg) {
if (uMsg->cmd != U2FHID_MSG) if (uMsg.cmd != U2FHID_MSG)
throw runtime_error{ "Failed to get U2F Msg uMsg" }; throw runtime_error{ "Failed to get U2F Msg uMsg" };
else if (uMsg->data.size() < 4) { else if (uMsg.data.size() < 4) {
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_WRONG_LENGTH); U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_WRONG_LENGTH);
throw runtime_error{ "Msg data is incorrect size" }; throw runtime_error{ "Msg data is incorrect size" };
} }
U2F_Msg_CMD cmd; U2F_Msg_CMD cmd;
auto& dat = uMsg->data; auto& dat = uMsg.data;
cmd.cla = dat[0]; cmd.cla = dat[0];
if (cmd.cla != 0) { if (cmd.cla != 0) {
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_COMMAND_NOT_ALLOWED); U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_COMMAND_NOT_ALLOWED);
throw runtime_error{ "Invalid CLA value in U2F Message" }; throw runtime_error{ "Invalid CLA value in U2F Message" };
} }
@@ -84,11 +84,11 @@ shared_ptr<U2F_Msg_CMD> U2F_Msg_CMD::generate(const shared_ptr<U2FMessage> uMsg)
const auto cmdUsesData = usesData.find(cmd.ins); const auto cmdUsesData = usesData.find(cmd.ins);
if (cmdUsesData == usesData.end()) { if (cmdUsesData == usesData.end()) {
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_INS_NOT_SUPPORTED); U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_INS_NOT_SUPPORTED);
throw runtime_error{ "Unknown instruction: unsure if uses data" }; throw runtime_error{ "Unknown instruction: unsure if uses data" };
} else if (cmdUsesData->second || data.size() > 3) { } else if (cmdUsesData->second || data.size() > 3) {
if (cBCount == 0) { if (cBCount == 0) {
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_WRONG_LENGTH); U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_WRONG_LENGTH);
throw runtime_error{ "Invalid command - should have attached data" }; throw runtime_error{ "Invalid command - should have attached data" };
} }
@@ -106,7 +106,7 @@ shared_ptr<U2F_Msg_CMD> U2F_Msg_CMD::generate(const shared_ptr<U2FMessage> uMsg)
try { try {
cmd.le = getLe(data.end() - endPtr, vector<uint8_t>(endPtr, data.end())); cmd.le = getLe(data.end() - endPtr, vector<uint8_t>(endPtr, data.end()));
} catch (runtime_error& ignored) { } catch (runtime_error& ignored) {
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_WRONG_LENGTH); U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_WRONG_LENGTH);
throw; throw;
} }
} else { } else {
@@ -116,7 +116,7 @@ shared_ptr<U2F_Msg_CMD> U2F_Msg_CMD::generate(const shared_ptr<U2FMessage> uMsg)
try { try {
cmd.le = getLe(cBCount, data); cmd.le = getLe(cBCount, data);
} catch (runtime_error& ignored) { } catch (runtime_error& ignored) {
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_WRONG_LENGTH); U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_WRONG_LENGTH);
throw; throw;
} }
} }
@@ -175,7 +175,7 @@ shared_ptr<U2F_Msg_CMD> U2F_Msg_CMD::generate(const shared_ptr<U2FMessage> uMsg)
throw APDU_STATUS::SW_INS_NOT_SUPPORTED; throw APDU_STATUS::SW_INS_NOT_SUPPORTED;
} }
} catch (const APDU_STATUS e) { } catch (const APDU_STATUS e) {
U2F_Msg_CMD::error(uMsg->cid, e); U2F_Msg_CMD::error(uMsg.cid, e);
throw runtime_error{ "APDU construction error" }; throw runtime_error{ "APDU construction error" };
return {}; return {};
} }
@@ -194,13 +194,6 @@ const map<uint8_t, bool> U2F_Msg_CMD::usesData = { { U2F_REG, true },
{ U2F_AUTH, true }, { U2F_AUTH, true },
{ U2F_VER, false } }; { U2F_VER, false } };
void U2F_Msg_CMD::respond(const uint32_t channelID) const { void U2F_Msg_CMD::respond(const uint32_t channelID, bool) const {
U2F_Msg_CMD::error(channelID, static_cast<uint16_t>(APDU_STATUS::SW_INS_NOT_SUPPORTED)); U2F_Msg_CMD::error(channelID, static_cast<uint16_t>(APDU_STATUS::SW_INS_NOT_SUPPORTED));
} }
bool U2F_Msg_CMD::modifiesPersistentState() const {
const auto usesPersistentState = usesData.find(ins);
// Be conservative for future instructions. Assume that they do modify persist state.
return (usesPersistentState == usesData.end()) || usesPersistentState->second;
}

View File

@@ -36,10 +36,10 @@ struct U2F_Msg_CMD : public U2F_CMD {
protected: protected:
static uint32_t getLe(const uint32_t byteCount, std::vector<uint8_t> bytes); static uint32_t getLe(const uint32_t byteCount, std::vector<uint8_t> bytes);
U2F_Msg_CMD() = default; U2F_Msg_CMD() = default;
virtual ~U2F_Msg_CMD() = default;
public: public:
static std::shared_ptr<U2F_Msg_CMD> generate(const std::shared_ptr<U2FMessage> uMsg); static std::shared_ptr<U2F_Msg_CMD> generate(const U2FMessage& uMsg);
static void error(const uint32_t channelID, const uint16_t errCode); static void error(const uint32_t channelID, const uint16_t errCode);
void respond(const uint32_t channelID) const override; void respond(const uint32_t channelID, bool hasAuthorisation) const;
virtual bool modifiesPersistentState() const override;
}; };

View File

@@ -21,19 +21,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
using namespace std; using namespace std;
U2F_Ping_CMD::U2F_Ping_CMD(const shared_ptr<U2FMessage> uMsg) : nonce{ uMsg->data } { U2F_Ping_CMD::U2F_Ping_CMD(const U2FMessage& uMsg) : nonce{ uMsg.data } {
if (uMsg->cmd != U2FHID_PING) if (uMsg.cmd != U2FHID_PING)
throw runtime_error{ "Failed to get U2F ping message" }; throw runtime_error{ "Failed to get U2F ping message" };
} }
void U2F_Ping_CMD::respond(const uint32_t channelID) const { void U2F_Ping_CMD::respond(const uint32_t channelID, bool) const {
U2FMessage msg{}; U2FMessage msg{};
msg.cid = channelID; msg.cid = channelID;
msg.cmd = U2FHID_PING; msg.cmd = U2FHID_PING;
msg.data = nonce; msg.data = nonce;
msg.write(); msg.write();
} }
bool U2F_Ping_CMD::modifiesPersistentState() const {
return false;
}

View File

@@ -22,12 +22,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
class U2F_Ping_CMD : public U2F_CMD { struct U2F_Ping_CMD : U2F_CMD {
protected:
std::vector<uint8_t> nonce; std::vector<uint8_t> nonce;
public: public:
U2F_Ping_CMD(const std::shared_ptr<U2FMessage> uMsg); U2F_Ping_CMD(const U2FMessage& uMsg);
virtual void respond(const uint32_t channelID) const override; virtual void respond(const uint32_t channelID, bool) const override;
virtual bool modifiesPersistentState() const override;
}; };

View File

@@ -61,7 +61,13 @@ U2F_Register_APDU::U2F_Register_APDU(const U2F_Msg_CMD& msg, const vector<uint8_
Storage::keyCounts[this->keyH] = 0; Storage::keyCounts[this->keyH] = 0;
} }
void U2F_Register_APDU::respond(const uint32_t channelID) const { void U2F_Register_APDU::respond(const uint32_t channelID, bool hasAuthorisation) const {
if (!hasAuthorisation) {
error(channelID, APDU_STATUS::SW_CONDITIONS_NOT_SATISFIED);
return;
}
U2FMessage m{}; U2FMessage m{};
m.cid = channelID; m.cid = channelID;
m.cmd = U2FHID_MSG; m.cmd = U2FHID_MSG;
@@ -118,3 +124,7 @@ void U2F_Register_APDU::respond(const uint32_t channelID) const {
m.write(); m.write();
} }
bool U2F_Register_APDU::requiresAuthorisation() const {
return true;
}

View File

@@ -20,14 +20,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "Storage.hpp" #include "Storage.hpp"
#include "U2F_Msg_CMD.hpp" #include "U2F_Msg_CMD.hpp"
class U2F_Register_APDU : public U2F_Msg_CMD { struct U2F_Register_APDU : U2F_Msg_CMD {
protected:
std::array<uint8_t, 32> challengeP; std::array<uint8_t, 32> challengeP;
Storage::AppParam appP; Storage::AppParam appP;
Storage::KeyHandle keyH; Storage::KeyHandle keyH;
public: public:
U2F_Register_APDU(const U2F_Msg_CMD& msg, const std::vector<uint8_t>& data); U2F_Register_APDU(const U2F_Msg_CMD& msg, const std::vector<uint8_t>& data);
virtual ~U2F_Register_APDU() = default;
void respond(const uint32_t channelID) const override; bool requiresAuthorisation() const override;
void respond(const uint32_t channelID, bool hasAuthorisation) const override;
}; };

View File

@@ -33,7 +33,7 @@ U2F_Version_APDU::U2F_Version_APDU(const U2F_Msg_CMD& msg, const std::vector<uin
throw APDU_STATUS::SW_WRONG_LENGTH; throw APDU_STATUS::SW_WRONG_LENGTH;
} }
void U2F_Version_APDU::respond(const uint32_t channelID) const { void U2F_Version_APDU::respond(const uint32_t channelID, bool) const {
char ver[]{ 'U', '2', 'F', '_', 'V', '2' }; char ver[]{ 'U', '2', 'F', '_', 'V', '2' };
U2FMessage m{}; U2FMessage m{};

View File

@@ -19,8 +19,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once #pragma once
#include "U2F_Msg_CMD.hpp" #include "U2F_Msg_CMD.hpp"
class U2F_Version_APDU : public U2F_Msg_CMD { struct U2F_Version_APDU : U2F_Msg_CMD {
public: public:
U2F_Version_APDU(const U2F_Msg_CMD& msg, const std::vector<uint8_t>& data); U2F_Version_APDU(const U2F_Msg_CMD& msg, const std::vector<uint8_t>& data);
void respond(const uint32_t channelID) const override; void respond(const uint32_t channelID, bool) const override;
}; };

40
_Architecture.hpp Normal file
View File

@@ -0,0 +1,40 @@
/*
U2FDevice - A program to allow Raspberry Pi Zeros to act as U2F tokens
Copyright (C) 2018 Michael Kuc
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#define ARCH_RASPBERRY_PI 1
#define ARCH_ANDROID 2
#define ARCHITECTURE ARCH_ANDROID
#if ARCHITECTURE == ARCH_RASPBERRY_PI
# define STORAGE_PREFIX "/usr/share/"
# define HID_DEV "/dev/hidg0"
# define DEBUG_STREAMS "/tmp/"
// #define DEBUG_MSGS
# define LEDS
#elif ARCHITECTURE == ARCH_ANDROID
# include <string>
# define STORAGE_PREFIX "/sdcard/U2F/"
extern std::string hidDev;
# define HID_DEV hidDev.c_str()
# define DEBUG_STREAMS "/sdcard/log"
# define HID_SOCKET
# define MANUAL_LIFETIME
// #define DEBUG_MSGS
#endif

View File

@@ -20,7 +20,7 @@ void terminateHandler() {
int main(int argc, char** argv) { int main(int argc, char** argv) {
#ifdef DEBUG_MSGS #ifdef DEBUG_MSGS
std::set_terminate(terminateHandler); std::set_terminate(terminate_handler);
#endif #endif
int retCode = handleTransactions(argv[0], argc == 2 ? argv[1] : STORAGE_PREFIX); int retCode = handleTransactions(argv[0], argc == 2 ? argv[1] : STORAGE_PREFIX);

Binary file not shown.