From da9a91681c0636936830e04d8ac1bf16568fcf0e Mon Sep 17 00:00:00 2001 From: Michael Kuc Date: Sun, 5 Aug 2018 19:32:07 +0000 Subject: [PATCH] Implemented authentication. Near feature complete. --- .gitignore | 7 ++- .gitmodules | 3 + APDU.hpp | 12 +++- Base64.tpp | 25 ++++---- Field.cpp | 8 +++ Field.hpp | 10 ++- Field.tpp | 8 +++ Makefile | 10 ++- Packet.cpp | 113 +++++++++++++++++++++++++++++---- Scripts/cpp-base64-Makefile | 20 ++++++ Signature.cpp | 29 +++++++++ Signature.hpp | 9 +++ Storage.cpp | 42 ++++++++----- Storage.hpp | 3 + Streams.cpp | 88 ++++++++++++++++++++++++-- Streams.hpp | 2 + U2FMessage.cpp | 37 ++++++++++- U2F_Authenticate_APDU.cpp | 120 ++++++++++++++++++++++++++++++++++++ U2F_Authenticate_APDU.hpp | 23 +++++++ U2F_Init_CMD.cpp | 3 + U2F_Init_Response.hpp | 4 ++ U2F_Msg_CMD.cpp | 57 ++++++++++++++--- U2F_Register_APDU.cpp | 69 +++++++++++---------- U2F_Version_APDU.cpp | 28 +++++++++ U2F_Version_APDU.hpp | 9 +++ cpp-base64 | 1 + monitor.cpp | 16 ++++- schema/APDU.html | 81 ++++++++++++++++++++++++ schema/packets.html | 78 +++++++++++++++++++++++ 29 files changed, 816 insertions(+), 99 deletions(-) create mode 100644 Field.cpp create mode 100644 Field.tpp create mode 100755 Scripts/cpp-base64-Makefile create mode 100644 Signature.cpp create mode 100644 Signature.hpp create mode 100644 U2F_Authenticate_APDU.cpp create mode 100644 U2F_Authenticate_APDU.hpp create mode 100644 U2F_Version_APDU.cpp create mode 100644 U2F_Version_APDU.hpp create mode 160000 cpp-base64 create mode 100644 schema/APDU.html create mode 100644 schema/packets.html diff --git a/.gitignore b/.gitignore index 3698563..567fbef 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,11 @@ SOut.txt comdev.txt comhost.txt libuECC.o +libcppb64.o monitor obj/* U2F_Priv_Keys.txt -devpackets.txt -hostpackets.txt +devAPDU.html +devpackets.html +hostAPDU.html +hostpackets.html diff --git a/.gitmodules b/.gitmodules index aa2f181..7b53ca0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "micro-ecc"] path = micro-ecc url = https://github.com/kmackay/micro-ecc.git +[submodule "cpp-base64"] + path = cpp-base64 + url = https://github.com/ReneNyffenegger/cpp-base64.git diff --git a/APDU.hpp b/APDU.hpp index f0f8f76..c489b06 100644 --- a/APDU.hpp +++ b/APDU.hpp @@ -1,8 +1,18 @@ #pragma once -enum APDU +enum APDU : uint8_t { U2F_REG = 0x01, U2F_AUTH = 0x02, U2F_VER = 0x03 }; + +enum APDU_STATUS : uint16_t +{ + SW_NO_ERROR = 0x9000, + SW_CONDITIONS_NOT_SATISFIED = 0x6985, + SW_WRONG_DATA = 0x6A80, + SW_WRONG_LENGTH = 0x6700, + SW_CLA_NOT_SUPPORTED = 0x6E00, + SW_INS_NOT_SUPPORTED = 0x6D00 +}; diff --git a/Base64.tpp b/Base64.tpp index 8c76097..45d2616 100644 --- a/Base64.tpp +++ b/Base64.tpp @@ -1,9 +1,10 @@ #include "Base64.hpp" #include -#include -#include +#include "cpp-base64/base64.h" #include +#include #include +#include "Field.hpp" template void b64encode(const InContainer &iContainer, OutContainer &oContainer) @@ -32,22 +33,18 @@ void b64decode(const InContainer &iContainer, std::array &oArr) template void b64encode(const InContainerIter beginIter, const InContainerIter endIter, OutContainerIter oBeginIter) { - std::stringstream oss, iss{ std::string{ beginIter, endIter } }; - base64::encoder encoder{}; - encoder.encode(iss, oss); + std::vector rawBytes{}; - auto oStr = oss.str(); - oStr.erase(std::remove_if(oStr.begin(), oStr.end(), [](auto charac){ return charac == '\n'; }), oStr.end()); - copy(oStr.begin(), oStr.end(), oBeginIter); + for (auto iter = beginIter; iter != endIter; iter++) + rawBytes.insert(rawBytes.end(), FIELD(*iter)); + + auto encoded = base64_encode(rawBytes.data(), rawBytes.size()); + std::copy(encoded.begin(), encoded.end(), oBeginIter); } template void b64decode(const InContainerIter beginIter, const InContainerIter endIter, OutContainerIter oBeginIter) { - std::stringstream oss, iss{ std::string{ beginIter, endIter } }; - base64::decoder decoder{}; - decoder.decode(iss, oss); - - auto oStr = oss.str(); - copy(oStr.begin(), oStr.end(), oBeginIter); + auto decoded = base64_decode(std::string{ beginIter, endIter }); + std::copy(decoded.begin(), decoded.end(), oBeginIter); } diff --git a/Field.cpp b/Field.cpp new file mode 100644 index 0000000..14425e2 --- /dev/null +++ b/Field.cpp @@ -0,0 +1,8 @@ +#include "Field.hpp" + +using namespace std; + +vector beEncode(const uint8_t* val, const size_t byteCount) +{ + return { reverse_iterator(val + byteCount), reverse_iterator(val) }; +} diff --git a/Field.hpp b/Field.hpp index 88807d4..35570fb 100644 --- a/Field.hpp +++ b/Field.hpp @@ -1,3 +1,11 @@ #pragma once +#include +#include -#define FIELD(name) reinterpret_cast(&name), (reinterpret_cast(&name) + sizeof(name)) +template +std::vector beEncode(const Type val); + +std::vector beEncode(const uint8_t* val, const std::size_t byteCount); + +#define FIELD(name) reinterpret_cast(&name), (reinterpret_cast(&name) + sizeof(name)) +#define FIELD_BE(name) reverse_iterator(reinterpret_cast(&name) + sizeof(name)), reverse_iterator(reinterpret_cast(&name)) diff --git a/Field.tpp b/Field.tpp new file mode 100644 index 0000000..6a45f14 --- /dev/null +++ b/Field.tpp @@ -0,0 +1,8 @@ +#pragma once +#include "Field.hpp" + +template +std::vector beEncode(const Type val) +{ + return beEncode(reinterpret_cast(&val), sizeof(val)); +} diff --git a/Makefile b/Makefile index 93a36f6..9a2eb53 100755 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ SRC_DIR := . OBJ_DIR := obj -LDFLAGS := -lmbedcrypto -lb64 +LDFLAGS := -lmbedcrypto CPPFLAGS := CXXFLAGS := --std=c++14 @@ -10,7 +10,7 @@ CXXFLAGS += -MMD -MP MODULES := $(wildcard $(SRC_DIR)/*.cpp) OBJECTS := $(MODULES:$(SRC_DIR)/%.cpp=$(OBJ_DIR)/%.o) -monitor: $(OBJECTS) libuECC.o +monitor: $(OBJECTS) libuECC.o libcppb64.o g++ $(LDFLAGS) -o $@ $^ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp @@ -22,7 +22,11 @@ clean: rm $(OBJ_DIR)/* rm monitor -.PHONY: libuECC +.PHONY: libuECC.o libcppb64.so clean libuECC.o: $(MAKE) -C micro-ecc cp micro-ecc/libuECC.o libuECC.o + +libcppb64.o: + $(MAKE) -C cpp-base64 + cp cpp-base64/libcppb64.o libcppb64.o diff --git a/Packet.cpp b/Packet.cpp index 2710e91..8f8321c 100644 --- a/Packet.cpp +++ b/Packet.cpp @@ -19,8 +19,32 @@ shared_ptr InitPacket::getPacket(const uint32_t rCID, const uint8_t copy(dataBytes.begin(), dataBytes.end(), p->data.begin()); auto hPStream = getHostPacketStream().get(); - fprintf(hPStream, "\n"); - fwrite(dataBytes.data(), 1, dataBytes.size(), hPStream); + fprintf(hPStream, "\t\t\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t
CIDCMDBCNTHBCNTLDATA
0x%08X%u%u%u", p->cid, p->cmd, p->bcnth, p->bcntl); + + for (auto elem : dataBytes) + fprintf(hPStream, "%3u ", elem); + + fprintf(hPStream, "
" + "\t\t
"); clog << "Fully read init packet" << endl; return p; @@ -36,9 +60,30 @@ shared_ptr ContPacket::getPacket(const uint32_t rCID, const uint8_t copy(dataBytes.begin(), dataBytes.end(), p->data.begin()); auto hPStream = getHostPacketStream().get(); - fwrite(dataBytes.data(), 1, dataBytes.size(), hPStream); + fprintf(hPStream, "\t\t\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t
CIDSEQDATA
0x%08X%u", p->cid, p->seq); - clog << "Fully read cont packet" << endl; + for (auto elem : dataBytes) + fprintf(hPStream, "%3u ", elem); + + fprintf(hPStream, "
\n" + "\t\t
"); + + //clog << "Fully read cont packet" << endl; return p; } @@ -47,7 +92,7 @@ shared_ptr Packet::getPacket() const uint32_t cid = *reinterpret_cast(readBytes(4).data()); uint8_t b = readBytes(1)[0]; - clog << "Packet read 2nd byte as " << static_cast(b) << endl; + //clog << "Packet read 2nd byte as " << static_cast(b) << endl; if (b & TYPE_MASK) { @@ -80,11 +125,36 @@ void InitPacket::writePacket() fwrite(this->buf, packetSize, 1, hostStream); fwrite(this->buf, packetSize, 1, devStream); - perror(nullptr); + if (errno != 0) + perror("perror " __FILE__ " 85"); auto dPStream = getDevPacketStream().get(); - fprintf(dPStream, "\n"); - fwrite(data.data(), 1, data.size(), dPStream); + fprintf(dPStream, "\t\t\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t
CIDCMDBCNTHBCNTLDATA
0x%08X%u%u%u", cid, cmd, bcnth, bcntl); + + for (auto elem : data) + fprintf(dPStream, "%3u ", elem); + + fprintf(dPStream, "
" + "\t\t
"); clog << "Fully wrote init packet" << endl; } @@ -100,10 +170,31 @@ void ContPacket::writePacket() fwrite(this->buf, packetSize, 1, hostStream); fwrite(this->buf, packetSize, 1, devStream); - perror(nullptr); + if (errno != 0) + perror("perror " __FILE__ " 107"); auto dPStream = getDevPacketStream().get(); - fwrite(data.data(), 1, data.size(), dPStream); - clog << "Fully wrote cont packet" << endl; + fprintf(dPStream, "\t\t\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t
CIDSEQDATA
0x%08X%u", cid, seq); + + for (auto elem : data) + fprintf(dPStream, "%3u ", elem); + + fprintf(dPStream, "
\n" + "\t\t
"); } diff --git a/Scripts/cpp-base64-Makefile b/Scripts/cpp-base64-Makefile new file mode 100755 index 0000000..190eb49 --- /dev/null +++ b/Scripts/cpp-base64-Makefile @@ -0,0 +1,20 @@ +#!/usr/bin/env make + +SRC_DIR := . +OBJ_DIR := . +LDFLAGS := -r +CPPFLAGS := +CXXFLAGS := --std=c++14 + +CXXFLAGS += -MMD -MP +MODULESC := $(SRC_DIR)/base64.cpp +OBJECTS := $(MODULESC:$(SRC_DIR)/%.cpp=$(OBJ_DIR)/%.o) + +libcppb64.o: $(OBJECTS) + ld $(LDFLAGS) -o $@ $^ + +$(OBJECTS): $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp + @mkdir -p $(@D) + @g++ $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +-include $(OBJECTS:.o=.d) diff --git a/Signature.cpp b/Signature.cpp new file mode 100644 index 0000000..dee0561 --- /dev/null +++ b/Signature.cpp @@ -0,0 +1,29 @@ +#include "Signature.hpp" +#include + +using namespace std; + +//Ripped from https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp +void appendSignatureAsDER(vector &response, const array &signature) +{ + response.push_back(0x30); // Start of ASN.1 SEQUENCE + response.push_back(68); //total length from (2 * (32 + 2)) to (2 * (33 + 2)) + size_t countByte = response.size() - 1; + + // Loop twice - for R and S + for(unsigned int i = 0; i < 2; i++) + { + unsigned int sigOffs = i * 32; + auto offset = signature.begin() + sigOffs; + response.push_back(0x02); //header: integer + response.push_back(32); //32 byte + if (signature[sigOffs] > 0x7f) + { + // Integer needs to be represented in 2's completement notion + response.back()++; + response.push_back(0); // add leading 0, to indicate it is a positive number + response[countByte]++; + } + copy(offset, offset + 32, back_inserter(response)); //R or S value + } +} diff --git a/Signature.hpp b/Signature.hpp new file mode 100644 index 0000000..8039f1d --- /dev/null +++ b/Signature.hpp @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +using Digest = std::array; +using Signature = std::array; + +//Ripped from https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp +void appendSignatureAsDER(std::vector &response, const Signature &signature); diff --git a/Storage.cpp b/Storage.cpp index 45c8b4a..4a66b29 100644 --- a/Storage.cpp +++ b/Storage.cpp @@ -10,6 +10,7 @@ std::string Storage::filename{}; std::map Storage::appParams{}; std::map Storage::privKeys{}; std::map Storage::pubKeys{}; +std::map Storage::keyCounts{}; void Storage::init(const string &dirPrefix) { @@ -18,30 +19,27 @@ void Storage::init(const string &dirPrefix) string line; size_t lineNumber = 0; - base64::decoder decoder{}; - while (getline(file, line)) { auto strLineNum = to_string(lineNumber); stringstream ss{ line }; - string keyHStr, appStr, privStr, pubStr; - ss >> keyHStr >> appStr >> privStr >> pubStr; + string keyHStr, appStr, privStr, pubStr, keyCStr; + ss >> keyHStr >> appStr >> privStr >> pubStr >> keyCStr; if (!ss) throw runtime_error{ string{ "Invalid syntax of line " } + strLineNum }; char *endP = nullptr; - Storage::KeyHandle keyH{ strtoull(keyHStr.c_str(), &endP, 10) }; + auto keyH{ static_cast(strtoull(keyHStr.c_str(), &endP, 10)) }; if (!endP) throw runtime_error{ "Invalid keyhandle format on line " + strLineNum }; + + endP = nullptr; + auto keyC{ static_cast(strtoull(keyCStr.c_str(), &endP, 10)) }; - //if (appStr.size() != 32) - // throw runtime_error{ "Invalid length of app parameter on line " + strLineNum }; - //if (privStr.size() != 32) - // throw runtime_error{ "Invalid length of private key on line " + strLineNum }; - //if (pubStr.size() != 65) - // throw runtime_error{ "Invalid length of public key on line " + strLineNum }; + if (!endP) + throw runtime_error{ "Invalid key count format on line " + strLineNum }; Storage::AppParam appParam{}; b64decode(appStr, appParam); @@ -52,9 +50,17 @@ void Storage::init(const string &dirPrefix) Storage::PubKey pubKey{}; b64decode(pubStr, pubKey); + clog << "Loaded key with pubkey: " << hex; + + for (auto b : pubKey) + clog << static_cast(b) << ' '; + + clog << dec << endl; + Storage::appParams[keyH] = appParam; Storage::privKeys[keyH] = privKey; Storage::pubKeys[keyH] = pubKey; + Storage::keyCounts[keyH] = keyC; lineNumber++; } @@ -63,14 +69,14 @@ void Storage::init(const string &dirPrefix) void Storage::save() { ofstream file{ Storage::filename }; - base64::encoder encoder{}; for (auto &keypair : Storage::appParams) { - const auto keyID = keypair.first; - const auto appParam = keypair.second; - const auto privKey = Storage::privKeys[keypair.first]; - const auto pubKey = Storage::pubKeys[keypair.first]; + const auto& keyID = keypair.first; + const auto& appParam = keypair.second; + const auto& privKey = Storage::privKeys[keyID]; + const auto& pubKey = Storage::pubKeys[keyID]; + const auto& keyCount = Storage::keyCounts[keyID]; file << keyID; file << ' '; @@ -85,6 +91,8 @@ void Storage::save() string pubKStr{}; b64encode(pubKey, pubKStr); - file << pubKStr << endl; + file << pubKStr << ' '; + + file << keyCount << endl; } } diff --git a/Storage.hpp b/Storage.hpp index bf0703d..33d20a6 100644 --- a/Storage.hpp +++ b/Storage.hpp @@ -1,3 +1,4 @@ +#pragma once #include #include #include @@ -7,6 +8,7 @@ class Storage { public: using KeyHandle = uint32_t; + using KeyCount = uint32_t; using AppParam = std::array; using PrivKey = std::array; using PubKey = std::array; @@ -22,4 +24,5 @@ public: static std::map appParams; static std::map privKeys; static std::map pubKeys; + static std::map keyCounts; }; diff --git a/Streams.cpp b/Streams.cpp index f921762..2015dfe 100644 --- a/Streams.cpp +++ b/Streams.cpp @@ -3,6 +3,9 @@ using namespace std; +FILE* initHTML(FILE *fPtr, const string &title); +void closeHTML(FILE *fPtr); + shared_ptr getHostStream() { static shared_ptr stream{ fopen("/dev/hidg0", "ab+"), [](FILE *f){ @@ -30,9 +33,22 @@ shared_ptr getComHostStream() shared_ptr getHostPacketStream() { - static shared_ptr stream{ fopen("hostpackets.txt", "wb"), [](FILE *f){ + static shared_ptr stream{ initHTML(fopen("hostpackets.html", "wb"), "Host Packets"), [](FILE *f){ clog << "Closing hostPackets stream" << endl; - fclose(f); + closeHTML(f); + } }; + + if (!stream) + clog << "Stream is unavailable" << endl; + + return stream; +} + +shared_ptr getHostAPDUStream() +{ + static shared_ptr stream{ initHTML(fopen("hostAPDU.html", "wb"), "Host APDU"), [](FILE *f){ + clog << "Closing host APDU stream" << endl; + closeHTML(f); } }; if (!stream) @@ -56,9 +72,9 @@ shared_ptr getComDevStream() shared_ptr getDevPacketStream() { - static shared_ptr stream{ fopen("devpackets.txt", "wb"), [](FILE *f){ + static shared_ptr stream{ initHTML(fopen("devpackets.html", "wb"), "Dev Packets"), [](FILE *f){ clog << "Closing devPackets stream" << endl; - fclose(f); + closeHTML(f); } }; if (!stream) @@ -66,3 +82,67 @@ shared_ptr getDevPacketStream() return stream; } + +shared_ptr getDevAPDUStream() +{ + static shared_ptr stream{ initHTML(fopen("devAPDU.html", "wb"), "Dev APDU"), [](FILE *f){ + clog << "Closing dev APDU stream" << endl; + closeHTML(f); + } }; + + if (!stream) + clog << "Stream is unavailable" << endl; + + return stream; +} + +FILE* initHTML(FILE *fPtr, const string &title) +{ + fprintf(fPtr, "\n" + "\t\n" + "\t\t%s\n" + "\t\t\n" + "\t\n" + "\n" + "\t", title.c_str()); + + return fPtr; +} + +void closeHTML(FILE *fPtr) +{ + fprintf(fPtr, "\t\n" + ""); + fclose(fPtr); +} diff --git a/Streams.hpp b/Streams.hpp index 2d67797..c0d87e3 100644 --- a/Streams.hpp +++ b/Streams.hpp @@ -5,5 +5,7 @@ std::shared_ptr getHostStream(); std::shared_ptr getComHostStream(); std::shared_ptr getHostPacketStream(); +std::shared_ptr getHostAPDUStream(); std::shared_ptr getComDevStream(); std::shared_ptr getDevPacketStream(); +std::shared_ptr getDevAPDUStream(); diff --git a/U2FMessage.cpp b/U2FMessage.cpp index 8ce7557..e8ae197 100644 --- a/U2FMessage.cpp +++ b/U2FMessage.cpp @@ -2,7 +2,9 @@ #include "Packet.hpp" #include #include +#include #include "Streams.hpp" +#include "u2f.hpp" using namespace std; @@ -34,19 +36,22 @@ U2FMessage U2FMessage::read() throw runtime_error{ "Packet out of sequence" }; const uint16_t remainingBytes = messageSize - message.data.size(); - clog << "Remaining bytes: " << remainingBytes << endl; const uint16_t copyBytes = min(static_cast(newPack->data.size()), remainingBytes); message.data.insert(message.data.end(), newPack->data.begin(), newPack->data.begin() + copyBytes); currSeq++; } + std::clog << "Read all of message" << std::endl; + return message; } void U2FMessage::write() { + clog << "Flushing host stream" << endl; fflush(getHostStream().get()); + clog << "Flushed host stream" << endl; const uint16_t bytesToWrite = this->data.size(); uint16_t bytesWritten = 0; @@ -80,8 +85,38 @@ void U2FMessage::write() copy(data.begin() + bytesWritten, data.begin() + bytesWritten + newByteCount, p.data.begin()); p.writePacket(); seq++; + bytesWritten += newByteCount; } auto stream = getHostStream().get(); fflush(stream); + + if (cmd == U2FHID_MSG) + { + auto dAS = getDevAPDUStream().get(); + + fprintf(dAS, "\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t
DATAERR
"); + + for (size_t i = 0; i < data.size() - 2; i++) + fprintf(dAS, "%3u ", data[i]); + + uint16_t err = data[data.size() - 2] << 8; + err |= data.back(); + + fprintf(dAS, "0x%04X
\n" + "\t\t
", err); + } } diff --git a/U2F_Authenticate_APDU.cpp b/U2F_Authenticate_APDU.cpp new file mode 100644 index 0000000..a66d2b1 --- /dev/null +++ b/U2F_Authenticate_APDU.cpp @@ -0,0 +1,120 @@ +#include "U2F_Authenticate_APDU.hpp" +#include "Field.hpp" +#include "U2FMessage.hpp" +#include "u2f.hpp" +#include "Field.tpp" +#include "APDU.hpp" +#include +#include "Signature.hpp" +#include "micro-ecc/uECC.h" +#include + +using namespace std; + +U2F_Authenticate_APDU::U2F_Authenticate_APDU(const U2F_Msg_CMD &msg, const vector &data) + : U2F_Msg_CMD{ msg } +{ + if (data.size() < 66) + throw runtime_error{ "Invalid authentication request" }; + + copy(data.begin() + 0, data.begin() + 32, challengeP.begin()); + copy(data.begin() + 32, data.begin() + 64, appParam.begin()); + + uint8_t keyHLen = data[64]; + + copy(data.begin() + 65, data.begin() + 65 + keyHLen, back_inserter(keyH)); + + clog << "Got U2F_Auth request" << endl; +} + +void U2F_Authenticate_APDU::respond() +{ + U2FMessage msg{}; + msg.cid = 0xF1D0F1D0; + msg.cmd = U2FHID_MSG; + auto statusCode = APDU_STATUS::SW_NO_ERROR; + + auto &response = msg.data; + + if (keyH.size() != sizeof(Storage::KeyHandle)) + { + //Respond with error code - key handle is of wrong size + clog << "Invalid key handle length" << endl; + statusCode = APDU_STATUS::SW_WRONG_DATA; + response.insert(response.end(), FIELD_BE(statusCode)); + msg.write(); + return; + } + + auto keyHB = *reinterpret_cast(keyH.data()); + + if (Storage::appParams.find(keyHB) == Storage::appParams.end()) + { + //Respond with error code - key handle doesn't exist in storage + clog << "Invalid key handle" << endl; + statusCode = APDU_STATUS::SW_WRONG_DATA; + response.insert(response.end(), FIELD_BE(statusCode)); + msg.write(); + return; + } + + auto appMatches = (Storage::appParams.at(keyHB) == appParam); + + switch (p1) + { + case ControlCode::CheckOnly: + if (appMatches) + statusCode = APDU_STATUS::SW_CONDITIONS_NOT_SATISFIED; + else + statusCode = APDU_STATUS::SW_WRONG_DATA; + + response.insert(response.end(), FIELD_BE(statusCode)); + msg.write(); + return; + case ControlCode::EnforcePresenceSign: + //Continue processing + case ControlCode::DontEnforcePresenceSign: + //Continue processing + break; + + default: + cerr << "Unknown APDU command" << endl; + statusCode = APDU_STATUS::SW_WRONG_DATA; + response.insert(response.end(), FIELD_BE(statusCode)); + msg.write(); + return; + } + + const auto& privKey = Storage::privKeys[keyHB]; + auto& keyCount = Storage::keyCounts[keyHB]; + keyCount++; + + response.push_back(0x01); + response.insert(response.end(), FIELD_BE(keyCount)); + + Digest digest; + { + mbedtls_sha256_context shaContext; + + mbedtls_sha256_init(&shaContext); + mbedtls_sha256_starts(&shaContext, 0); + + mbedtls_sha256_update(&shaContext, reinterpret_cast(appParam.data()), sizeof(appParam)); + uint8_t userPresence{ 1u }; + mbedtls_sha256_update(&shaContext, &userPresence, 1); + const auto beCounter = beEncode(keyCount); + mbedtls_sha256_update(&shaContext, beCounter.data(), beCounter.size()); + mbedtls_sha256_update(&shaContext, challengeP.data(), challengeP.size()); + + mbedtls_sha256_finish(&shaContext, digest.data()); + mbedtls_sha256_free(&shaContext); + } + + Signature signature{}; + uECC_sign(privKey.data(), digest.data(), digest.size(), signature.data(), uECC_secp256r1()); + + appendSignatureAsDER(response, signature); + response.insert(response.end(), FIELD_BE(statusCode)); + + msg.write(); +} diff --git a/U2F_Authenticate_APDU.hpp b/U2F_Authenticate_APDU.hpp new file mode 100644 index 0000000..6023c40 --- /dev/null +++ b/U2F_Authenticate_APDU.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "U2F_Msg_CMD.hpp" +#include "Storage.hpp" + +struct U2F_Authenticate_APDU : U2F_Msg_CMD +{ + uint8_t controlByte; + std::array challengeP; + Storage::AppParam appParam; + std::vector keyH; + + public: + U2F_Authenticate_APDU(const U2F_Msg_CMD &msg, const std::vector &data); + + void respond(); + + enum ControlCode + { + CheckOnly = 0x07, + EnforcePresenceSign = 0x03, + DontEnforcePresenceSign = 0x08 + }; +}; diff --git a/U2F_Init_CMD.cpp b/U2F_Init_CMD.cpp index 6b7db01..ef67671 100644 --- a/U2F_Init_CMD.cpp +++ b/U2F_Init_CMD.cpp @@ -2,6 +2,7 @@ #include #include "U2FMessage.hpp" #include "u2f.hpp" +#include using namespace std; @@ -17,5 +18,7 @@ U2F_Init_CMD U2F_Init_CMD::get() U2F_Init_CMD cmd; cmd.nonce = *reinterpret_cast(message.data.data()); + clog << "Fully read nonce" << endl; + return cmd; } diff --git a/U2F_Init_Response.hpp b/U2F_Init_Response.hpp index b3b3109..619d14e 100644 --- a/U2F_Init_Response.hpp +++ b/U2F_Init_Response.hpp @@ -14,6 +14,7 @@ struct U2F_Init_Response : U2F_CMD void write() { + std::clog << "Beginning writeout of U2F_Init_Response" << std::endl; U2FMessage m{}; m.cid = CID_BROADCAST; m.cmd = U2FHID_INIT; @@ -26,6 +27,9 @@ struct U2F_Init_Response : U2F_CMD m.data.insert(m.data.begin() + 15, FIELD(buildDevVer)); m.data.insert(m.data.begin() + 16, FIELD(capabilities)); + std::clog << "Finished inserting U2F_Init_Response fields to data buffer" << std::endl; + m.write(); + std::clog << "Completed writeout of U2F_Init_Response" << std::endl; } }; diff --git a/U2F_Msg_CMD.cpp b/U2F_Msg_CMD.cpp index 313813c..a85bdfd 100644 --- a/U2F_Msg_CMD.cpp +++ b/U2F_Msg_CMD.cpp @@ -1,10 +1,13 @@ #include "U2F_Msg_CMD.hpp" #include "APDU.hpp" #include "U2F_Register_APDU.hpp" +#include "U2F_Version_APDU.hpp" +#include "U2F_Authenticate_APDU.hpp" #include "U2FMessage.hpp" #include "u2f.hpp" #include "APDU.hpp" #include +#include "Streams.hpp" using namespace std; @@ -58,10 +61,9 @@ shared_ptr U2F_Msg_CMD::get() vector data{ dat.begin() + 4, dat.end() }; auto startPtr = data.begin(), endPtr = data.end(); - if (usesData.at(cmd.ins)) + if (usesData.at(cmd.ins) || data.size() > 3) { - clog << "Assuming command uses data" << endl; - clog << "First bytes are: " << static_cast(data[0]) << " " << static_cast(data[1]) << " " << static_cast(data[2]) << endl; + //clog << "First bytes are: " << static_cast(data[0]) << " " << static_cast(data[1]) << " " << static_cast(data[2]) << endl; if (cBCount == 0) throw runtime_error{ "Invalid command - should have attached data" }; @@ -77,15 +79,12 @@ shared_ptr U2F_Msg_CMD::get() startPtr += 3; } - clog << "Got command uses " << cmd.lc << " bytes of data" << endl; - endPtr = startPtr + cmd.lc; cmd.le = getLe(data.end() - endPtr, vector(endPtr, data.end())); } else { - clog << "Assuming command does not use data" << endl; cmd.lc = 0; endPtr = startPtr; cmd.le = getLe(cBCount, data); @@ -93,8 +92,50 @@ shared_ptr U2F_Msg_CMD::get() const auto dBytes = vector(startPtr, endPtr); - if (cmd.ins == APDU::U2F_REG) - return make_shared(cmd, dBytes); + auto hAS = getHostAPDUStream().get(); + + fprintf(hAS, "\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\t\n" + "\t\t\t\t\n" + "\t\t\t\n" + "\t\t
CLAINSP1P2LcDataLe
0x%02X0x%02X%u%u%3u", cmd.cla, cmd.ins, cmd.p1, cmd.p2, cmd.lc); + + for (auto b : dBytes) + fprintf(hAS, "%3u ", b); + + fprintf(hAS, "%5u
\n" + "\t\t
", cmd.le); + + switch (cmd.ins) + { + case APDU::U2F_REG: + return make_shared(cmd, dBytes); + case APDU::U2F_AUTH: + return make_shared(cmd, dBytes); + case APDU::U2F_VER: + return make_shared(cmd); + default: + throw runtime_error{ "Unknown APDU command issued" }; + } } void U2F_Msg_CMD::respond(){}; diff --git a/U2F_Register_APDU.cpp b/U2F_Register_APDU.cpp index 678ba99..6b2e78c 100644 --- a/U2F_Register_APDU.cpp +++ b/U2F_Register_APDU.cpp @@ -5,6 +5,10 @@ #include "Certificates.hpp" #include #include +#include "APDU.hpp" +#include "U2FMessage.hpp" +#include "u2f.hpp" +#include "Signature.hpp" using namespace std; @@ -23,63 +27,51 @@ U2F_Register_APDU::U2F_Register_APDU(const U2F_Msg_CMD &msg, const vectorkeyH = Storage::appParams.size(); + this->keyH = Storage::appParams.size(); //To increment the key handle Storage::appParams[this->keyH] = appP; Storage::privKeys[this->keyH] = privKey; Storage::pubKeys[this->keyH] = pubKey; -} + Storage::keyCounts[this->keyH] = 0; -//Ripped from https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp -void appendSignatureAsDER(vector &response, const array &signature) { - response.push_back(0x30); // Start of ASN.1 SEQUENCE - response.push_back(68); //total length from (2 * (32 + 2)) to (2 * (33 + 2)) - auto &count = response.back(); + clog << "Produced pub key: " << hex; - // Loop twice - for R and S - for(unsigned int i = 0; i < 2; i++) { - unsigned int sigOffs = i * 32; - response.push_back(0x02); //header: integer - response.push_back(32); //32 byte - if (signature[sigOffs] > 0x7f) { // Integer needs to be represented in 2's completement notion - response.back()++; - response.push_back(0); // add leading 0, to indicate it is a positive number - count++; - } - copy(signature.begin() + sigOffs, signature.begin() + sigOffs + 32, back_inserter(response)); //R or S value - } + for (auto b : pubKey) + clog << static_cast(b) << ' '; + + clog << endl << dec << "Got U2F_Reg request" << endl; } void U2F_Register_APDU::respond() { + U2FMessage m{}; + m.cid = 0xF1D0F1D0; + m.cmd = U2FHID_MSG; + + auto& response = m.data; const auto appParam = Storage::appParams[this->keyH]; const auto pubKey = Storage::pubKeys[this->keyH]; const auto privKey = Storage::privKeys[this->keyH]; - vector response; response.push_back(0x05); copy(pubKey.begin(), pubKey.end(), back_inserter(response)); response.push_back(sizeof(this->keyH)); + auto fakeKeyHBytes = reinterpret_cast(&this->keyH); - copy(fakeKeyHBytes, fakeKeyHBytes + 4, back_inserter(response)); + copy(fakeKeyHBytes, fakeKeyHBytes + sizeof(this->keyH), back_inserter(response)); + copy(attestCert, end(attestCert), back_inserter(response)); //Gen signature - array digest; + Digest digest; { mbedtls_sha256_context shaContext; @@ -97,13 +89,22 @@ void U2F_Register_APDU::respond() mbedtls_sha256_update(&shaContext, reinterpret_cast(pubKey.data()), pubKey.size()); - mbedtls_sha256_finish(&shaContext, reinterpret_cast(digest.data())); + mbedtls_sha256_finish(&shaContext, digest.data()); mbedtls_sha256_free(&shaContext); } - array signature; + Signature signature; + std::clog << "Will sign digest with priv key" << std::endl; uECC_sign(attestPrivKey, digest.data(), digest.size(), signature.data(), uECC_secp256r1()); //Append signature as DER + std::clog << "Will append sig as DER" << std::endl; appendSignatureAsDER(response, signature); + + response.push_back(static_cast(APDU_STATUS::SW_NO_ERROR) >> 8); + response.push_back(static_cast(APDU_STATUS::SW_NO_ERROR) & 0xff); + + std::clog << "Writing out " << response.size() << " bytes in response" << std::endl; + + m.write(); } diff --git a/U2F_Version_APDU.cpp b/U2F_Version_APDU.cpp new file mode 100644 index 0000000..2f70779 --- /dev/null +++ b/U2F_Version_APDU.cpp @@ -0,0 +1,28 @@ +#include "U2F_Version_APDU.hpp" +#include +#include "U2FMessage.hpp" +#include "u2f.hpp" +#include "Field.hpp" +#include "APDU.hpp" + +using namespace std; + +U2F_Version_APDU::U2F_Version_APDU(const U2F_Msg_CMD &msg) +{ + clog << "Got U2F_Ver APDU request" << endl; + + //Don't actually respond yet +} + +void U2F_Version_APDU::respond() +{ + char ver[]{ 'U', '2', 'F', '_', 'V', '2' }; + U2FMessage m{}; + + m.cid = 0xF1D0F1D0; + m.cmd = U2FHID_MSG; + m.data.insert(m.data.end(), FIELD(ver)); + auto sC = APDU_STATUS::SW_NO_ERROR; + m.data.insert(m.data.end(), FIELD_BE(sC)); + m.write(); +} diff --git a/U2F_Version_APDU.hpp b/U2F_Version_APDU.hpp new file mode 100644 index 0000000..fbc41e2 --- /dev/null +++ b/U2F_Version_APDU.hpp @@ -0,0 +1,9 @@ +#pragma once +#include "U2F_Msg_CMD.hpp" + +struct U2F_Version_APDU : U2F_Msg_CMD +{ + public: + U2F_Version_APDU(const U2F_Msg_CMD &msg); + void respond(); +}; diff --git a/cpp-base64 b/cpp-base64 new file mode 160000 index 0000000..6420804 --- /dev/null +++ b/cpp-base64 @@ -0,0 +1 @@ +Subproject commit 6420804f7ba10e8c3049c6e3b59ec88c88d808a6 diff --git a/monitor.cpp b/monitor.cpp index 602ded9..89f7f27 100644 --- a/monitor.cpp +++ b/monitor.cpp @@ -18,11 +18,12 @@ using namespace std; -int main() +int main(int argc, char** argv) { Storage::init(); auto initFrame = U2F_Init_CMD::get(); + U2F_Init_Response resp{}; resp.cid = 0xF1D0F1D0; @@ -35,9 +36,18 @@ int main() resp.write(); - auto m = U2F_Msg_CMD::get(); + size_t msgCount = (argc == 2 ? stoul(argv[1]) : 3u); - cout << "U2F CMD ins: " << static_cast(m->ins) << endl; + for (size_t i = 0; i < msgCount; i++) + { + auto reg = U2F_Msg_CMD::get(); + + cout << "U2F CMD ins: " << static_cast(reg->ins) << endl; + + reg->respond(); + + clog << "Fully responded" << endl; + } /*auto m = U2FMessage::read(); diff --git a/schema/APDU.html b/schema/APDU.html new file mode 100644 index 0000000..88355bb --- /dev/null +++ b/schema/APDU.html @@ -0,0 +1,81 @@ + + + Packets + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CLAINSP1P2LcDataLe
0x%X0x%X%u%u%u$DATA%u
+
+ + + + + + + + + + + + + +
DataERR
$DATA%X
+
+ + diff --git a/schema/packets.html b/schema/packets.html new file mode 100644 index 0000000..b27e85c --- /dev/null +++ b/schema/packets.html @@ -0,0 +1,78 @@ + + + Packets + + + + + + + + + + + + + + + + + + + + + + + +
CIDCMDBCNTHBCNTLDATA
%08X%u%u%u0x$DATA
+
+ + + + + + + + + + + + + + + +
CIDSEQDATA
%08X%u0x$DATA
+
+ +