diff --git a/Architecture.cpp b/Architecture.cpp new file mode 100644 index 0000000..652342e --- /dev/null +++ b/Architecture.cpp @@ -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 . +*/ + +#include "_Architecture.hpp" + +using namespace std; + +#if ARCHITECTURE == ARCH_ANDROID +string hidDev = "/dev/hidg2"; +#endif diff --git a/Architecture.hpp b/Architecture.hpp index d2053e6..f02b79e 100644 --- a/Architecture.hpp +++ b/Architecture.hpp @@ -18,22 +18,7 @@ along with this program. If not, see . #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 -# define STORAGE_PREFIX "/sdcard/U2F/" -# define HID_DEV "/dev/hidg2" -# define DEBUG_STREAMS "/data/local/tmp/" -// #define DEBUG_MSGS -#endif +#include "_Architecture.hpp" #undef ARCH_ANDROID #undef ARCH_RASPBERRY_PI diff --git a/IO.cpp b/IO.cpp index 127d5f7..2f136e1 100644 --- a/IO.cpp +++ b/IO.cpp @@ -29,9 +29,11 @@ along with this program. If not, see . #include #include #include +#include #include #include #include +#include #include #include #include @@ -41,12 +43,13 @@ using namespace std; bool bytesAvailable(size_t count); vector& getBuffer(); -string binaryDirectory{}; +#ifdef DEBUG_STREAMS string cacheDirectory{ DEBUG_STREAMS }; +#endif -// Thanks to https://stackoverflow.com/a/478960 -vector execOutput(const string& cmd); -void execInput(const string& cmd, const vector& stdinData); +#ifdef HID_SOCKET +string clientSocket{}; +#endif vector readNonBlock(const size_t count) { if (!bytesAvailable(count)) @@ -63,13 +66,25 @@ vector readNonBlock(const size_t count) { } void write(const vector& bytes) { - __android_log_print(ANDROID_LOG_DEBUG, "U2FAndroid", "Writing %zu bytes", bytes.size()); - execInput("su -c \"" + binaryDirectory + "/U2FAndroid_Write\"", bytes); + size_t totalBytes = 0; + auto hostDescriptor = *getHostDescriptor(); + + while (totalBytes < bytes.size()) { + auto writtenBytes = + send(hostDescriptor, bytes.data() + totalBytes, bytes.size() - totalBytes, 0); + + if (writtenBytes > 0) + totalBytes += writtenBytes; + else if (errno != 0 && errno != EAGAIN && + errno != EWOULDBLOCK) // Expect file blocking behaviour + ERR(); + } + + errno = 0; } bool bytesAvailable(const size_t count) { auto startTime = std::chrono::high_resolution_clock::now(); - const timespec iterDelay{ 0, 10000000 }; chrono::duration delay{ 0 }; while (delay.count() < U2FHID_TRANS_TIMEOUT) { @@ -79,7 +94,6 @@ bool bytesAvailable(const size_t count) { #endif return true; } - nanosleep(&iterDelay, nullptr); delay = chrono::high_resolution_clock::now() - startTime; } @@ -96,44 +110,49 @@ vector& bufferVar() { } vector& getBuffer() { - auto& buff = bufferVar(); - vector bytes = execOutput("su -c \"" + binaryDirectory + "/U2FAndroid_Read\""); + const timespec delay{ 0, 10'000'000 }; - if (!bytes.empty()) { - __android_log_print(ANDROID_LOG_DEBUG, "U2FAndroid", "Reading bytes: got %zu", - bytes.size()); - buff.insert(buff.end(), bytes.begin(), bytes.end()); + auto& buff = bufferVar(); + array bytes{}; + auto hostDescriptor = *getHostDescriptor(); + + pollfd pFD{ hostDescriptor, POLLIN }; + + while (true) { + ppoll(&pFD, 1, &delay, 0); + + if (pFD.revents & POLLIN) { + 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 + cerr << "Only retrieved " << readByteCount << " bytes from expected full packet." + << endl; +#endif + } + + if (readByteCount > 0) { + copy(bytes.begin(), bytes.begin() + readByteCount, back_inserter(buff)); #ifdef DEBUG_STREAMS - fwrite(bytes.data(), 1, bytes.size(), getComHostStream().get()); + fwrite(bytes.data(), 1, readByteCount, getComHostStream().get()); #endif + + } else if (errno != EAGAIN && errno != EWOULDBLOCK) // Expect read would block + { + ERR(); +#ifdef DEBUG_MSGS + cerr << "Unknown stream error: " << errno << endl; +#endif + break; + } else { + errno = 0; + break; // Escape loop if blocking would occur + } + } else + break; } return buff; } - -vector execOutput(const string& cmd) { - // NOLINT(hicpp-member-init) - array buffer; - vector result{}; - unique_ptr pipe{ popen(cmd.c_str(), "rb"), pclose }; - - if (!pipe) - throw std::runtime_error("popen() failed!"); - while (size_t readBytes = fread(buffer.data(), 1, buffer.size(), pipe.get())) - copy(buffer.begin(), buffer.begin() + readBytes, back_inserter(result)); - - return result; -} - -void execInput(const string& cmd, const vector& stdinData) { - assert(stdinData.size() % HID_RPT_SIZE == 0); - - size_t writtenBytes = 0; - unique_ptr pipe{ popen(cmd.c_str(), "wb"), pclose }; - - if (!pipe) - throw std::runtime_error("popen() failed!"); - while (writtenBytes < stdinData.size()) - writtenBytes += fwrite(stdinData.data(), 1, HID_RPT_SIZE, pipe.get()); -} diff --git a/IO.hpp b/IO.hpp index 8a39680..2706f6e 100644 --- a/IO.hpp +++ b/IO.hpp @@ -21,8 +21,13 @@ along with this program. If not, see . #include #include -extern std::string binaryDirectory; +#ifdef DEBUG_STREAMS extern std::string cacheDirectory; +#endif + +#ifdef HID_SOCKET +extern std::string clientSocket; +#endif // Returns either the number of bytes specified, // or returns empty vector without discarding bytes from HID stream diff --git a/Streams.cpp b/Streams.cpp index 93c20fc..1706482 100644 --- a/Streams.cpp +++ b/Streams.cpp @@ -17,36 +17,46 @@ along with this program. If not, see . */ #include "Streams.hpp" +#include "Architecture.hpp" #include "IO.hpp" +#include #include #include #include +#include #include #include +#include #include -using namespace std; +using std::cerr; +using std::clog; +using std::endl; +using std::runtime_error; +using std::shared_ptr; +using std::string; + +int initialiseHostDescriptor(); #ifdef DEBUG_STREAMS + FILE* initHTML(FILE* fPtr, const string& title); + void closeHTML(FILE* fPtr); + #endif shared_ptr getHostDescriptor() { - static shared_ptr descriptor{}; - - descriptor.reset(new int{ open(HID_DEV, O_RDWR | O_NONBLOCK | O_APPEND) }, [](int* fd) { - close(*fd); - delete fd; - }); - - if (*descriptor == -1) - throw runtime_error{ "Descriptor is unavailable" }; + static shared_ptr descriptor{ new int{ initialiseHostDescriptor() }, [](const int* fd) { + close(*fd); + delete fd; + } }; return descriptor; } #ifdef DEBUG_STREAMS + shared_ptr getComHostStream() { static shared_ptr stream{ fopen((cacheDirectory + "comhost.txt").c_str(), "wb"), [](FILE* f) { @@ -182,4 +192,49 @@ void closeHTML(FILE* fPtr) { ""); fclose(fPtr); } + #endif + +int initialiseHostDescriptor() { + int descriptor; + +#ifdef HID_SOCKET + 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(100000); + } + + 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"); +#else + descriptor = open(HID_DEV, O_RDWR | O_NONBLOCK | O_APPEND); + + if (descriptor == -1) + throw runtime_error{ "Descriptor is unavailable" }; +#endif + + return descriptor; +} diff --git a/_Architecture.hpp b/_Architecture.hpp new file mode 100644 index 0000000..cf31db8 --- /dev/null +++ b/_Architecture.hpp @@ -0,0 +1,39 @@ +/* +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 . +*/ + +#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 +# define STORAGE_PREFIX "/sdcard/U2F/" +extern std::string hidDev; +# define HID_DEV hidDev.c_str() +# define DEBUG_STREAMS "/sdcard/log/" +# define HID_SOCKET +// #define DEBUG_MSGS +#endif