Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b0d990f708 | |||
| 8a62dee131 | |||
| 4dac7dd014 | |||
| 3cc7032cf4 | |||
| 152bc721e1 | |||
| 89b4b7d949 | |||
| f8d077634e | |||
| c14c6d0f0d | |||
| 1497e620c1 | |||
| 5921e6ea48 | |||
| dac4617d70 | |||
| 01dad2ee0a | |||
| 03dba21ad4 | |||
| 0606772cc3 | |||
| 2987cbe26e | |||
| 2f8e417d00 | |||
| 87ac9c5946 | |||
| 4ffdeb3eda | |||
| f3fa1d29ba | |||
| 0523b49c72 | |||
| e6739cc5af |
24
.clang-format
Normal file
24
.clang-format
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
BasedOnStyle: llvm
|
||||
IndentWidth: 4
|
||||
---
|
||||
AccessModifierOffset: -4
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakBeforeBraces: Attach
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
Cpp11BracedListStyle: false
|
||||
ColumnLimit: 100
|
||||
DerivePointerAlignment: false
|
||||
FixNamespaceComments: true
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: AfterHash
|
||||
Language: Cpp
|
||||
PointerAlignment: Left
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
Standard: Cpp11
|
||||
TabWidth: 4
|
||||
UseTab: ForIndentation
|
||||
|
||||
...
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -3,3 +3,8 @@ libcppb64.a
|
||||
U2FDevice
|
||||
obj/*
|
||||
U2F_Priv_Keys.txt
|
||||
.ccls-cache
|
||||
compile_commands.json
|
||||
Certificates.hpp
|
||||
Certificates.cpp
|
||||
Keys/*
|
||||
|
||||
20
APDU.hpp
20
APDU.hpp
@@ -18,19 +18,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
enum APDU : uint8_t
|
||||
{
|
||||
U2F_REG = 0x01,
|
||||
U2F_AUTH = 0x02,
|
||||
U2F_VER = 0x03
|
||||
};
|
||||
enum APDU : uint8_t { U2F_REG = 0x01, U2F_AUTH = 0x02, U2F_VER = 0x03 };
|
||||
|
||||
enum APDU_STATUS : uint16_t
|
||||
{
|
||||
SW_NO_ERROR = 0x9000,
|
||||
SW_WRONG_LENGTH = 0x6700,
|
||||
enum APDU_STATUS : uint16_t {
|
||||
SW_NO_ERROR = 0x9000,
|
||||
SW_WRONG_LENGTH = 0x6700,
|
||||
SW_CONDITIONS_NOT_SATISFIED = 0x6985,
|
||||
SW_WRONG_DATA = 0x6A80,
|
||||
SW_INS_NOT_SUPPORTED = 0x6D00,
|
||||
SW_COMMAND_NOT_ALLOWED = 0x6E00,
|
||||
SW_WRONG_DATA = 0x6A80,
|
||||
SW_INS_NOT_SUPPORTED = 0x6D00,
|
||||
SW_COMMAND_NOT_ALLOWED = 0x6E00,
|
||||
};
|
||||
|
||||
@@ -16,9 +16,10 @@ 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
|
||||
#include <cstdint>
|
||||
#include "_Architecture.hpp"
|
||||
|
||||
extern uint8_t attestCert[557];
|
||||
extern uint8_t attestPrivKey[32];
|
||||
extern uint8_t attestPubKey[65];
|
||||
using namespace std;
|
||||
|
||||
#if ARCHITECTURE == ARCH_ANDROID
|
||||
string hidDev = "/dev/hidg2";
|
||||
#endif
|
||||
@@ -18,22 +18,7 @@ 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
|
||||
#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
|
||||
|
||||
14
Base64.hpp
14
Base64.hpp
@@ -19,19 +19,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#pragma once
|
||||
|
||||
template <typename InContainer, typename OutContainer>
|
||||
void b64encode(const InContainer &iContainer, OutContainer &oContainer);
|
||||
void b64encode(const InContainer& iContainer, OutContainer& oContainer);
|
||||
|
||||
template <typename InContainer, typename OutContainer>
|
||||
void b64decode(const InContainer &container, OutContainer &oContainer);
|
||||
void b64decode(const InContainer& container, OutContainer& oContainer);
|
||||
|
||||
template <typename InContainer, typename Elem, size_t count>
|
||||
void b64encode(const InContainer &iContainer, std::array<Elem, count> &oArr);
|
||||
void b64encode(const InContainer& iContainer, std::array<Elem, count>& oArr);
|
||||
|
||||
template <typename InContainer, typename Elem, size_t count>
|
||||
void b64decode(const InContainer &iContainer, std::array<Elem, count> &oArr);
|
||||
void b64decode(const InContainer& iContainer, std::array<Elem, count>& oArr);
|
||||
|
||||
template <typename InContainerIter, typename OutContainerIter>
|
||||
void b64encode(const InContainerIter beginIter, const InContainerIter endIter, OutContainerIter oBeginIter);
|
||||
void b64encode(const InContainerIter beginIter, const InContainerIter endIter,
|
||||
OutContainerIter oBeginIter);
|
||||
|
||||
template <typename InContainerIter, typename OutContainerIter>
|
||||
void b64decode(const InContainerIter beginIter, const InContainerIter endIter, OutContainerIter oBeginIter);
|
||||
void b64decode(const InContainerIter beginIter, const InContainerIter endIter,
|
||||
OutContainerIter oBeginIter);
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
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 "Certificates.hpp"
|
||||
|
||||
//You may not actually want to use these values -
|
||||
// having been shared publicly, these may be vulnerable to exploits.
|
||||
// However, generating your own attestation certificate makes your device
|
||||
// uniquely identifiable across platforms / services, etc.
|
||||
// You can generate your own using the method detailed in the README.
|
||||
|
||||
uint8_t attestCert[] = {
|
||||
0x30, 0x82, 0x02, 0x29, 0x30, 0x82, 0x01, 0xd0, 0xa0, 0x03, 0x02, 0x01,
|
||||
0x02, 0x02, 0x09, 0x00, 0x8a, 0xe2, 0x21, 0x3f, 0x2f, 0x8b, 0x72, 0x52,
|
||||
0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02,
|
||||
0x30, 0x70, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
|
||||
0x02, 0x55, 0x4b, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
|
||||
0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||
0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67,
|
||||
0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x31,
|
||||
0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x20, 0x55, 0x32,
|
||||
0x46, 0x20, 0x4b, 0x65, 0x79, 0x20, 0x74, 0x50, 0x46, 0x53, 0x71, 0x54,
|
||||
0x71, 0x6f, 0x5a, 0x6d, 0x62, 0x37, 0x38, 0x61, 0x6a, 0x6f, 0x2f, 0x75,
|
||||
0x58, 0x50, 0x73, 0x51, 0x3d, 0x3d, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x38,
|
||||
0x30, 0x36, 0x33, 0x30, 0x31, 0x39, 0x30, 0x37, 0x35, 0x31, 0x5a, 0x17,
|
||||
0x0d, 0x32, 0x38, 0x30, 0x36, 0x32, 0x37, 0x31, 0x39, 0x30, 0x37, 0x35,
|
||||
0x31, 0x5a, 0x30, 0x70, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
|
||||
0x06, 0x13, 0x02, 0x55, 0x4b, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
|
||||
0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61,
|
||||
0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c,
|
||||
0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69,
|
||||
0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74,
|
||||
0x64, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x20,
|
||||
0x55, 0x32, 0x46, 0x20, 0x4b, 0x65, 0x79, 0x20, 0x74, 0x50, 0x46, 0x53,
|
||||
0x71, 0x54, 0x71, 0x6f, 0x5a, 0x6d, 0x62, 0x37, 0x38, 0x61, 0x6a, 0x6f,
|
||||
0x2f, 0x75, 0x58, 0x50, 0x73, 0x51, 0x3d, 0x3d, 0x30, 0x59, 0x30, 0x13,
|
||||
0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
|
||||
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x32,
|
||||
0x41, 0xc3, 0xb8, 0x96, 0x97, 0xd8, 0x90, 0x66, 0x41, 0x88, 0x96, 0xd4,
|
||||
0x73, 0xb6, 0x37, 0xf7, 0x85, 0x29, 0xaf, 0x3b, 0x15, 0x0f, 0x83, 0x61,
|
||||
0x67, 0xea, 0xc9, 0xb2, 0xdb, 0x82, 0xb3, 0x2c, 0x99, 0x60, 0x8a, 0x98,
|
||||
0x7c, 0xd4, 0x04, 0xa0, 0x92, 0x22, 0x05, 0xaa, 0xf7, 0x7a, 0x91, 0x02,
|
||||
0x03, 0xdd, 0x15, 0x88, 0x87, 0x6a, 0x26, 0xe9, 0xee, 0xcf, 0x99, 0xb1,
|
||||
0x66, 0xc0, 0x01, 0xa3, 0x53, 0x30, 0x51, 0x30, 0x1d, 0x06, 0x03, 0x55,
|
||||
0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xcf, 0x7f, 0xfa, 0x7d, 0xc4, 0x8d,
|
||||
0xba, 0x60, 0x52, 0x4c, 0xb6, 0x16, 0x2e, 0x88, 0x62, 0xc7, 0x8c, 0xfc,
|
||||
0xe0, 0x63, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
|
||||
0x16, 0x80, 0x14, 0xcf, 0x7f, 0xfa, 0x7d, 0xc4, 0x8d, 0xba, 0x60, 0x52,
|
||||
0x4c, 0xb6, 0x16, 0x2e, 0x88, 0x62, 0xc7, 0x8c, 0xfc, 0xe0, 0x63, 0x30,
|
||||
0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30,
|
||||
0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce,
|
||||
0x3d, 0x04, 0x03, 0x02, 0x03, 0x47, 0x00, 0x30, 0x44, 0x02, 0x20, 0x72,
|
||||
0x25, 0x89, 0xc1, 0x32, 0x54, 0x66, 0xf8, 0x0e, 0x58, 0x77, 0xe3, 0xb5,
|
||||
0x62, 0x47, 0x33, 0x18, 0x5a, 0xdc, 0x28, 0x6a, 0x4a, 0x56, 0xcb, 0x58,
|
||||
0x63, 0xe3, 0xa1, 0x02, 0x6a, 0xf0, 0xd8, 0x02, 0x20, 0x65, 0x26, 0x84,
|
||||
0x7c, 0xc3, 0x3b, 0x7d, 0x6a, 0x22, 0x0c, 0x22, 0x3d, 0xc8, 0x43, 0xb7,
|
||||
0x84, 0x8b, 0x7b, 0x48, 0x23, 0xb0, 0x1e, 0x13, 0x35, 0x1d, 0x1a, 0x90,
|
||||
0x44, 0x62, 0x6c, 0xab, 0x9b
|
||||
};
|
||||
|
||||
uint8_t attestPrivKey[] = {
|
||||
0x7e, 0xbd, 0x91, 0x05, 0x5a, 0x80, 0x9f, 0x36, 0xe5, 0x2f, 0xe0, 0xd0,
|
||||
0xa9, 0x63, 0x0c, 0x86, 0x04, 0xb1, 0x04, 0xe3, 0xd1, 0xfb, 0xd0, 0x83,
|
||||
0xc7, 0x2e, 0x2f, 0x34, 0xb6, 0xd6, 0xa4, 0xb2
|
||||
};
|
||||
|
||||
uint8_t attestPubKey[] = {
|
||||
0x04, 0x32, 0x41, 0xc3, 0xb8, 0x96, 0x97, 0xd8, 0x90, 0x66, 0x41, 0x88,
|
||||
0x96, 0xd4, 0x73, 0xb6, 0x37, 0xf7, 0x85, 0x29, 0xaf, 0x3b, 0x15, 0x0f,
|
||||
0x83, 0x61, 0x67, 0xea, 0xc9, 0xb2, 0xdb, 0x82, 0xb3, 0x2c, 0x99, 0x60,
|
||||
0x8a, 0x98, 0x7c, 0xd4, 0x04, 0xa0, 0x92, 0x22, 0x05, 0xaa, 0xf7, 0x7a,
|
||||
0x91, 0x02, 0x03, 0xdd, 0x15, 0x88, 0x87, 0x6a, 0x26, 0xe9, 0xee, 0xcf,
|
||||
0x99, 0xb1, 0x66, 0xc0, 0x01
|
||||
};
|
||||
37
Certificates.cpp.template
Normal file
37
Certificates.cpp.template
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
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 "Certificates.hpp"
|
||||
|
||||
// You may not actually want to use these values -
|
||||
// having been shared publicly, these may be vulnerable to exploits.
|
||||
// However, generating your own attestation certificate makes your device
|
||||
// uniquely identifiable across platforms / services, etc.
|
||||
// You can generate your own using the method detailed in the README.
|
||||
|
||||
uint8_t attestCert[] = {
|
||||
// Generate attestation certificate here
|
||||
};
|
||||
|
||||
uint8_t attestPrivKey[] = {
|
||||
// Generate private key here
|
||||
};
|
||||
|
||||
uint8_t attestPubKey[] = {
|
||||
// Generate public key here
|
||||
};
|
||||
24
Certificates.hpp.template
Normal file
24
Certificates.hpp.template
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
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
|
||||
#include <cstdint>
|
||||
|
||||
extern uint8_t attestCert[/* attestation certificate size */];
|
||||
extern uint8_t attestPrivKey[/* attestation private key size */];
|
||||
extern uint8_t attestPubKey[/* attestation public key size */];
|
||||
43
Channel.cpp
43
Channel.cpp
@@ -17,27 +17,27 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Channel.hpp"
|
||||
#include <stdexcept>
|
||||
#include "u2f.hpp"
|
||||
#include "U2F_CMD.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <android/log.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Channel::Channel(const uint32_t channelID)
|
||||
: cid{ channelID }, initState{ ChannelInitState::Unitialised }, lockedState{ ChannelLockedState::Unlocked }
|
||||
{}
|
||||
: cid{ channelID }, initState{ ChannelInitState::Unitialised }, lockedState{
|
||||
ChannelLockedState::Unlocked
|
||||
} {}
|
||||
|
||||
uint32_t Channel::getCID() const
|
||||
{
|
||||
uint32_t Channel::getCID() const {
|
||||
return cid;
|
||||
}
|
||||
|
||||
void Channel::handle(const shared_ptr<U2FMessage> uMsg)
|
||||
{
|
||||
if (uMsg->cmd == U2FHID_INIT)
|
||||
bool Channel::handle(const U2FMessage& uMsg, AuthorisationLevel auth) {
|
||||
if (uMsg.cmd == U2FHID_INIT)
|
||||
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" };
|
||||
|
||||
if (this->initState != ChannelInitState::Initialised)
|
||||
@@ -47,16 +47,27 @@ void Channel::handle(const shared_ptr<U2FMessage> uMsg)
|
||||
|
||||
auto cmd = U2F_CMD::get(uMsg);
|
||||
|
||||
if (cmd)
|
||||
return cmd->respond(this->cid);
|
||||
if (!cmd)
|
||||
return true;
|
||||
|
||||
if (cmd->requiresAuthorisation()) {
|
||||
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) {
|
||||
this->initState = newInitState;
|
||||
}
|
||||
|
||||
void Channel::lock(const ChannelLockedState newLockedState)
|
||||
{
|
||||
void Channel::lock(const ChannelLockedState newLockedState) {
|
||||
this->lockedState = newLockedState;
|
||||
}
|
||||
|
||||
50
Channel.hpp
50
Channel.hpp
@@ -17,34 +17,30 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "U2FMessage.hpp"
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "U2FMessage.hpp"
|
||||
|
||||
enum class ChannelInitState
|
||||
{
|
||||
Unitialised,
|
||||
Initialised
|
||||
};
|
||||
|
||||
enum class ChannelLockedState
|
||||
{
|
||||
Locked,
|
||||
Unlocked
|
||||
};
|
||||
|
||||
class Channel
|
||||
{
|
||||
protected:
|
||||
uint32_t cid;
|
||||
ChannelInitState initState;
|
||||
ChannelLockedState lockedState;
|
||||
|
||||
public:
|
||||
Channel(const uint32_t channelID);
|
||||
void handle(const std::shared_ptr<U2FMessage> uMsg);
|
||||
|
||||
uint32_t getCID() const;
|
||||
void init(const ChannelInitState newInitState);
|
||||
void lock(const ChannelLockedState newLockedState);
|
||||
enum class ChannelInitState { Unitialised, Initialised };
|
||||
|
||||
enum class ChannelLockedState { Locked, Unlocked };
|
||||
|
||||
enum class AuthorisationLevel { Unspecified, Unauthorised, Authorised };
|
||||
|
||||
class Channel {
|
||||
protected:
|
||||
uint32_t cid;
|
||||
ChannelInitState initState;
|
||||
ChannelLockedState lockedState;
|
||||
|
||||
public:
|
||||
Channel(const uint32_t channelID);
|
||||
|
||||
// Returns false if requires authorisation check
|
||||
// True otherwise
|
||||
bool handle(const U2FMessage& uMsg, AuthorisationLevel auth);
|
||||
|
||||
uint32_t getCID() const;
|
||||
void init(const ChannelInitState newInitState);
|
||||
void lock(const ChannelLockedState newLockedState);
|
||||
};
|
||||
|
||||
@@ -17,63 +17,61 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Controller.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include <iostream>
|
||||
#include "IO.hpp"
|
||||
#include "LED.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Controller::Controller(const uint32_t startChannel)
|
||||
: channels{}, currChannel{ startChannel }
|
||||
{}
|
||||
|
||||
void Controller::handleTransaction()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (channels.size() != 0 && chrono::duration_cast<chrono::seconds>(chrono::system_clock::now() - lastMessage) < chrono::seconds(5))
|
||||
toggleACTLED();
|
||||
else
|
||||
enableACTLED(false);
|
||||
}
|
||||
catch (runtime_error& ignored)
|
||||
{}
|
||||
Controller::Controller(const uint32_t startChannel) : channels{}, currChannel{ startChannel } {}
|
||||
|
||||
void Controller::handleTransaction() {
|
||||
auto msg = U2FMessage::readNonBlock();
|
||||
|
||||
if (!msg)
|
||||
return;
|
||||
|
||||
|
||||
handleTransaction(*msg, AuthorisationLevel::Unspecified);
|
||||
}
|
||||
|
||||
bool Controller::handleTransaction(const U2FMessage& msg, AuthorisationLevel auth) {
|
||||
try {
|
||||
if (channels.size() != 0 &&
|
||||
chrono::duration_cast<chrono::seconds>(chrono::system_clock::now() - lastMessage) <
|
||||
chrono::seconds(5))
|
||||
toggleACTLED();
|
||||
else
|
||||
enableACTLED(false);
|
||||
} catch (runtime_error& ignored) {
|
||||
}
|
||||
|
||||
lastMessage = chrono::system_clock::now();
|
||||
|
||||
auto opChannel = msg->cid;
|
||||
auto opChannelID = msg.cid;
|
||||
|
||||
if (msg->cmd == U2FHID_INIT)
|
||||
{
|
||||
opChannel = nextChannel();
|
||||
auto channel = Channel{ opChannel };
|
||||
|
||||
try
|
||||
{
|
||||
channels.emplace(opChannel, channel); //In case of wrap-around replace existing one
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
channels.insert(make_pair(opChannel, channel));
|
||||
if (msg.cmd == U2FHID_INIT) {
|
||||
opChannelID = nextChannel();
|
||||
auto channel = Channel{ opChannelID };
|
||||
try {
|
||||
channels.emplace(opChannelID, channel); // In case of wrap-around replace existing one
|
||||
} catch (...) {
|
||||
channels.insert(make_pair(opChannelID, channel));
|
||||
}
|
||||
} else if (channels.find(opChannelID) == channels.end()) {
|
||||
U2FMessage::error(opChannelID, ERR_CHANNEL_BUSY);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_MSGS
|
||||
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
|
||||
|
||||
channels.at(opChannel).handle(msg);
|
||||
return channels.at(opChannelID).handle(msg, auth);
|
||||
}
|
||||
|
||||
uint32_t Controller::nextChannel()
|
||||
{
|
||||
uint32_t Controller::nextChannel() {
|
||||
do
|
||||
currChannel++;
|
||||
while (currChannel == 0xFFFFFFFF || currChannel == 0);
|
||||
|
||||
@@ -17,20 +17,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <chrono>
|
||||
#include "Channel.hpp"
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
|
||||
class Controller
|
||||
{
|
||||
protected:
|
||||
std::map<uint32_t, Channel> channels;
|
||||
uint32_t currChannel;
|
||||
std::chrono::system_clock::time_point lastMessage;
|
||||
class Controller {
|
||||
protected:
|
||||
std::map<uint32_t, Channel> channels;
|
||||
uint32_t currChannel;
|
||||
std::chrono::system_clock::time_point lastMessage;
|
||||
|
||||
public:
|
||||
Controller(const uint32_t startChannel = 1);
|
||||
public:
|
||||
Controller(const uint32_t startChannel = 1);
|
||||
|
||||
void handleTransaction();
|
||||
uint32_t nextChannel();
|
||||
void handleTransaction();
|
||||
|
||||
// Returns false if required authentication
|
||||
// Returns true otherwise
|
||||
bool handleTransaction(const U2FMessage& msg, AuthorisationLevel auth);
|
||||
uint32_t nextChannel();
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using namespace std;
|
||||
|
||||
vector<uint8_t> beEncode(const uint8_t* val, const size_t byteCount)
|
||||
{
|
||||
return { reverse_iterator<const uint8_t *>(val + byteCount), reverse_iterator<const uint8_t *>(val) };
|
||||
vector<uint8_t> beEncode(const uint8_t* val, const size_t byteCount) {
|
||||
return { reverse_iterator<const uint8_t*>(val + byteCount),
|
||||
reverse_iterator<const uint8_t*>(val) };
|
||||
}
|
||||
|
||||
10
Field.hpp
10
Field.hpp
@@ -17,13 +17,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
template <typename Type>
|
||||
std::vector<uint8_t> beEncode(const Type val);
|
||||
|
||||
std::vector<uint8_t> beEncode(const uint8_t* val, const std::size_t byteCount);
|
||||
|
||||
#define FIELD(name) reinterpret_cast<const uint8_t*>(&name), (reinterpret_cast<const uint8_t*>(&name) + sizeof(name))
|
||||
#define FIELD_BE(name) reverse_iterator<const uint8_t*>(reinterpret_cast<const uint8_t*>(&name) + sizeof(name)), reverse_iterator<const uint8_t*>(reinterpret_cast<const uint8_t*>(&name))
|
||||
#define FIELD(name) \
|
||||
reinterpret_cast<const uint8_t*>(&name), \
|
||||
(reinterpret_cast<const uint8_t*>(&name) + sizeof(name))
|
||||
#define FIELD_BE(name) \
|
||||
reverse_iterator<const uint8_t*>(reinterpret_cast<const uint8_t*>(&name) + sizeof(name)), \
|
||||
reverse_iterator<const uint8_t*>(reinterpret_cast<const uint8_t*>(&name))
|
||||
|
||||
31
GenCertificates.sh
Executable file
31
GenCertificates.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
sed --version | grep "GNU" 2>&1 1>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
printf "Requires the GNU version of SED.\n"
|
||||
return -1
|
||||
fi
|
||||
|
||||
[ -d Keys ] || mkdir Keys
|
||||
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out Keys/ecprivkey.pem
|
||||
openssl ec -in Keys/ecprivkey.pem -pubout -out Keys/ecpubkey.pem 2>/dev/null
|
||||
openssl req -new -x509 -key Keys/ecprivkey.pem -out Keys/certificate.pem -days 3650
|
||||
openssl x509 -outform der -in Keys/certificate.pem -out Keys/certificate.der 2>/dev/null
|
||||
|
||||
certificate="$(xxd --include Keys/certificate.der | sed -e '0,/{/d;/};/,$d' -e 's/^\s\+/\t/g')"
|
||||
certificateSize="$(printf "%s" "${certificate}" | wc -w)"
|
||||
printf "%s" "${certificate}" | sed -e '/\/\/ Generate attestation certificate here/{r/dev/stdin
|
||||
d }' Certificates.cpp.template > Keys/Certificates.cpp.template.1
|
||||
|
||||
privkey="$(openssl ec -in Keys/ecprivkey.pem -pubout -text -noout 2>/dev/null | sed -e '0,/priv:/d;/pub:/,$d' -e 's/\s//g;s/:/, /g;' -e 's/^/\t/g;s/\s\+$//g' -e 's/\(\s\)/\10x/g')"
|
||||
privSize="$(printf "%s" "${privkey}" | wc -w)"
|
||||
printf "%s\n" "${privkey}" | sed -e '/\/\/ Generate private key here/{r/dev/stdin
|
||||
d }' Keys/Certificates.cpp.template.1 > Keys/Certificates.cpp.template.2
|
||||
|
||||
pubkey="$(openssl ec -in Keys/ecprivkey.pem -pubout -text -noout 2>/dev/null | sed -e '0,/pub:/d;/ASN1/,$d' -e 's/\s//g;s/:/, /g;' -e 's/^/\t/g;s/\s\+$//g' -e 's/\(\s\)/\10x/g')"
|
||||
pubSize="$(printf "%s" "${pubkey}" | wc -w)"
|
||||
printf "%s\n" "${pubkey}" | sed -e '/\/\/ Generate public key here/{r/dev/stdin
|
||||
d }' Keys/Certificates.cpp.template.2 > Certificates.cpp
|
||||
|
||||
sed -e "s/\\/\\* attestation certificate size \\*\\//${certificateSize}/; s/\\/\\* attestation private key size \\*\\//${privSize}/; s/\\/\\* attestation public key size \\*\\//${pubSize}/" Certificates.hpp.template > Certificates.hpp
|
||||
135
IO.cpp
135
IO.cpp
@@ -17,34 +17,45 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "IO.hpp"
|
||||
#include "Streams.hpp"
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
//#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
#include "u2f.hpp"
|
||||
#include "Macro.hpp"
|
||||
#include "Streams.hpp"
|
||||
#include "U2FDevice.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include <android/log.h>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <poll.h>
|
||||
#include <ratio>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool bytesAvailable(const size_t count);
|
||||
bool bytesAvailable(size_t count);
|
||||
vector<uint8_t>& getBuffer();
|
||||
|
||||
vector<uint8_t> readNonBlock(const size_t count)
|
||||
{
|
||||
if (!bytesAvailable(count))
|
||||
{
|
||||
return vector<uint8_t>{};
|
||||
}
|
||||
#ifdef DEBUG_STREAMS
|
||||
string cacheDirectory{ DEBUG_STREAMS };
|
||||
#endif
|
||||
|
||||
auto &buffer = getBuffer();
|
||||
#ifdef HID_SOCKET
|
||||
string clientSocket{};
|
||||
#endif
|
||||
|
||||
vector<uint8_t> readNonBlock(const size_t count) {
|
||||
if (!bytesAvailable(count))
|
||||
return vector<uint8_t>{};
|
||||
|
||||
auto& buffer = getBuffer();
|
||||
auto buffStart = buffer.begin(), buffEnd = buffer.begin() + count;
|
||||
vector<uint8_t> bytes{ buffStart, buffEnd };
|
||||
buffer.erase(buffStart, buffEnd);
|
||||
@@ -54,40 +65,36 @@ vector<uint8_t> readNonBlock(const size_t count)
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void write(const uint8_t* bytes, const size_t count)
|
||||
{
|
||||
void write(const vector<uint8_t>& bytes) {
|
||||
size_t totalBytes = 0;
|
||||
auto hostDescriptor = *getHostDescriptor();
|
||||
auto hostDescriptor = *getHostDescriptor();
|
||||
|
||||
while (totalBytes < count)
|
||||
{
|
||||
auto writtenBytes = write(hostDescriptor, bytes + totalBytes, count - totalBytes);
|
||||
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
|
||||
else if (errno != 0 && errno != EAGAIN &&
|
||||
errno != EWOULDBLOCK) // Expect file blocking behaviour
|
||||
ERR();
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
}
|
||||
|
||||
bool bytesAvailable(const size_t count)
|
||||
{
|
||||
bool bytesAvailable(const size_t count) {
|
||||
auto startTime = std::chrono::high_resolution_clock::now();
|
||||
const timespec iterDelay{ 0, 1000 };
|
||||
chrono::duration<double, milli> delay{ 0 };
|
||||
|
||||
while (delay.count() < U2FHID_TRANS_TIMEOUT && contProc)
|
||||
{
|
||||
delay = chrono::high_resolution_clock::now() - startTime;
|
||||
while (delay.count() < U2FHID_TRANS_TIMEOUT) {
|
||||
if (getBuffer().size() >= count) {
|
||||
#ifdef DEBUG_MSGS
|
||||
clog << "Requested " << count << " bytes" << endl;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
nanosleep(&iterDelay, nullptr);
|
||||
delay = chrono::high_resolution_clock::now() - startTime;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_MSGS
|
||||
@@ -97,52 +104,54 @@ bool bytesAvailable(const size_t count)
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<uint8_t>& bufferVar()
|
||||
{
|
||||
vector<uint8_t>& bufferVar() {
|
||||
static vector<uint8_t> buffer{};
|
||||
return buffer;
|
||||
}
|
||||
|
||||
vector<uint8_t>& getBuffer()
|
||||
{
|
||||
auto &buff = bufferVar();
|
||||
vector<uint8_t>& getBuffer() {
|
||||
const timespec delay{ 0, 10'000'000 };
|
||||
|
||||
auto& buff = bufferVar();
|
||||
array<uint8_t, HID_RPT_SIZE> bytes{};
|
||||
auto hostDescriptor = *getHostDescriptor();
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto readByteCount = read(hostDescriptor, bytes.data(), HID_RPT_SIZE);
|
||||
pollfd pFD{ hostDescriptor, POLLIN };
|
||||
|
||||
if (readByteCount > 0 && readByteCount != HID_RPT_SIZE)
|
||||
{
|
||||
//Failed to copy an entire packet in, so log this packet
|
||||
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;
|
||||
cerr << "Only retrieved " << readByteCount << " bytes from expected full packet."
|
||||
<< endl;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (readByteCount > 0)
|
||||
{
|
||||
copy(bytes.begin(), bytes.begin() + readByteCount, back_inserter(buff));
|
||||
if (readByteCount > 0) {
|
||||
copy(bytes.begin(), bytes.begin() + readByteCount, back_inserter(buff));
|
||||
|
||||
#ifdef DEBUG_STREAMS
|
||||
fwrite(bytes.data(), 1, readByteCount, getComHostStream().get());
|
||||
fwrite(bytes.data(), 1, readByteCount, getComHostStream().get());
|
||||
#endif
|
||||
|
||||
}
|
||||
else if (errno != EAGAIN && errno != EWOULDBLOCK) //Expect read would block
|
||||
{
|
||||
ERR();
|
||||
#if DEBUG_MSGS
|
||||
cerr << "Unknown stream error: " << errno << endl;
|
||||
} 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;
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = 0;
|
||||
break; //Escape loop if blocking would occur
|
||||
}
|
||||
}
|
||||
|
||||
return buff;
|
||||
|
||||
16
IO.hpp
16
IO.hpp
@@ -21,9 +21,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
//Returns either the number of bytes specified,
|
||||
//or returns empty vector without discarding bytes from HID stream
|
||||
#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
|
||||
std::vector<uint8_t> readNonBlock(const size_t count);
|
||||
|
||||
//Blocking write to HID stream - shouldn't block for too long
|
||||
void write(const uint8_t* bytes, const size_t count);
|
||||
// Blocking write to HID stream - shouldn't block for too long
|
||||
void write(const std::vector<uint8_t>& data);
|
||||
|
||||
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIH69kQVagJ825S/g0KljDIYEsQTj0fvQg8cuLzS21qSyoAoGCCqGSM49
|
||||
AwEHoUQDQgAEMkHDuJaX2JBmQYiW1HO2N/eFKa87FQ+DYWfqybLbgrMsmWCKmHzU
|
||||
BKCSIgWq93qRAgPdFYiHaibp7s+ZsWbAAQ==
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -1,4 +0,0 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMkHDuJaX2JBmQYiW1HO2N/eFKa87
|
||||
FQ+DYWfqybLbgrMsmWCKmHzUBKCSIgWq93qRAgPdFYiHaibp7s+ZsWbAAQ==
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -1 +0,0 @@
|
||||
~<7E><>Z<><5A>6<EFBFBD>/<2F>Щc<0C><04><04><><EFBFBD>Ѓ<EFBFBD>./4<>֤<EFBFBD>
|
||||
@@ -1 +0,0 @@
|
||||
2Aø<41><C3B8>ؐfA<66><41><EFBFBD>s<EFBFBD>7<EFBFBD><37>)<29>;<0F>ag<61>ɲۂ<C9B2>,<2C>`<60><>|<7C><04><>"<05><>z<EFBFBD><03><15><>j&<26><>ϙ<EFBFBD>f<EFBFBD>
|
||||
@@ -1,14 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICKTCCAdCgAwIBAgIJAIriIT8vi3JSMAoGCCqGSM49BAMCMHAxCzAJBgNVBAYT
|
||||
AlVLMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn
|
||||
aXRzIFB0eSBMdGQxKTAnBgNVBAMMIFUyRiBLZXkgdFBGU3FUcW9abWI3OGFqby91
|
||||
WFBzUT09MB4XDTE4MDYzMDE5MDc1MVoXDTI4MDYyNzE5MDc1MVowcDELMAkGA1UE
|
||||
BhMCVUsxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
|
||||
ZGdpdHMgUHR5IEx0ZDEpMCcGA1UEAwwgVTJGIEtleSB0UEZTcVRxb1ptYjc4YWpv
|
||||
L3VYUHNRPT0wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQyQcO4lpfYkGZBiJbU
|
||||
c7Y394UprzsVD4NhZ+rJstuCsyyZYIqYfNQEoJIiBar3epECA90ViIdqJunuz5mx
|
||||
ZsABo1MwUTAdBgNVHQ4EFgQUz3/6fcSNumBSTLYWLohix4z84GMwHwYDVR0jBBgw
|
||||
FoAUz3/6fcSNumBSTLYWLohix4z84GMwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjO
|
||||
PQQDAgNHADBEAiByJYnBMlRm+A5Yd+O1YkczGFrcKGpKVstYY+OhAmrw2AIgZSaE
|
||||
fMM7fWoiDCI9yEO3hIt7SCOwHhM1HRqQRGJsq5s=
|
||||
-----END CERTIFICATE-----
|
||||
15
LED.cpp
15
LED.cpp
@@ -24,19 +24,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool& ledState()
|
||||
{
|
||||
bool& ledState() {
|
||||
static bool state = true;
|
||||
return state;
|
||||
}
|
||||
|
||||
bool getLEDState()
|
||||
{
|
||||
bool getLEDState() {
|
||||
return ledState();
|
||||
}
|
||||
|
||||
void disableACTTrigger([[maybe_unused]] bool nowDisabled)
|
||||
{
|
||||
void disableACTTrigger([[maybe_unused]] bool nowDisabled) {
|
||||
#ifdef LEDS
|
||||
ofstream trigFile{ "/sys/class/leds/led0/trigger", ofstream::out | ofstream::trunc };
|
||||
|
||||
@@ -48,8 +45,7 @@ void disableACTTrigger([[maybe_unused]] bool nowDisabled)
|
||||
#endif
|
||||
}
|
||||
|
||||
void enableACTLED([[maybe_unused]] bool nowOn)
|
||||
{
|
||||
void enableACTLED([[maybe_unused]] bool nowOn) {
|
||||
#ifdef LEDS
|
||||
if (nowOn == getLEDState())
|
||||
return;
|
||||
@@ -66,7 +62,6 @@ void enableACTLED([[maybe_unused]] bool nowOn)
|
||||
#endif
|
||||
}
|
||||
|
||||
void toggleACTLED()
|
||||
{
|
||||
void toggleACTLED() {
|
||||
enableACTLED(!getLEDState());
|
||||
}
|
||||
|
||||
@@ -17,7 +17,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
#define ERR() if (errno != 0) perror((string{ "(" } + __FILE__ + ":" + to_string(__LINE__) + ")" + " " + __PRETTY_FUNCTION__).c_str()), errno = 0
|
||||
#define ERR() \
|
||||
if (errno != 0) \
|
||||
perror( \
|
||||
(string{ "(" } + __FILE__ + ":" + to_string(__LINE__) + ")" + " " + __PRETTY_FUNCTION__) \
|
||||
.c_str()), \
|
||||
errno = 0
|
||||
|
||||
12
Makefile
12
Makefile
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env make
|
||||
|
||||
SRC_DIR := .
|
||||
KEY_DIR := Keys
|
||||
OBJ_DIR := obj
|
||||
CXXFLAGS := -std=c++11 -MMD -MP -Wall -Wfatal-errors -Wextra -fPIE
|
||||
LDFLAGS := -fPIE
|
||||
@@ -31,7 +32,7 @@ install: U2FDevice
|
||||
install -m775 -t /etc/systemd/system Services/U2FDevice.service
|
||||
install -d /usr/share/U2FDevice/
|
||||
|
||||
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp | $(OBJ_DIR)
|
||||
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp | $(OBJ_DIR) $(SRC_DIR)/Certificates.hpp
|
||||
$(CXX) $(STATIC) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJ_DIR):
|
||||
@@ -45,7 +46,14 @@ clean:
|
||||
$(MAKE) -C micro-ecc clean
|
||||
$(MAKE) -C cpp-base64 clean
|
||||
|
||||
.PHONY: clean install
|
||||
clean-certificates:
|
||||
rm -f $(KEY_DIR)/*
|
||||
rm -f Certificates.cpp Certificates.hpp
|
||||
|
||||
$(SRC_DIR)/Certificates.hpp: $(SRC_DIR)/Certificates.hpp.template
|
||||
$(error "Please run the GenCertificates.sh script to generate certificate before building\n")
|
||||
|
||||
.PHONY: clean clean-certificates install
|
||||
|
||||
libuECC.a:
|
||||
$(MAKE) -C micro-ecc
|
||||
|
||||
235
Packet.cpp
235
Packet.cpp
@@ -18,24 +18,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "Packet.hpp"
|
||||
#include "IO.hpp"
|
||||
#include "Streams.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
#include "Streams.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
shared_ptr<InitPacket> InitPacket::getPacket(const uint32_t rCID, const uint8_t rCMD)
|
||||
{
|
||||
shared_ptr<InitPacket> InitPacket::getPacket(const uint32_t rCID, const uint8_t rCMD) {
|
||||
static size_t bytesRead = 0;
|
||||
static uint8_t bcnth;
|
||||
static uint8_t bcntl;
|
||||
static decltype(InitPacket::data) dataBytes;
|
||||
vector<uint8_t> bytes{};
|
||||
|
||||
switch (bytesRead)
|
||||
{
|
||||
switch (bytesRead) {
|
||||
case 0:
|
||||
bytes = readNonBlock(1);
|
||||
|
||||
@@ -62,7 +60,8 @@ shared_ptr<InitPacket> InitPacket::getPacket(const uint32_t rCID, const uint8_t
|
||||
if (bytes.size() == 0)
|
||||
return {};
|
||||
|
||||
copy(bytes.begin(), bytes.end(), dataBytes.begin());;
|
||||
copy(bytes.begin(), bytes.end(), dataBytes.begin());
|
||||
;
|
||||
bytesRead += bytes.size();
|
||||
[[fallthrough]];
|
||||
|
||||
@@ -74,56 +73,56 @@ shared_ptr<InitPacket> InitPacket::getPacket(const uint32_t rCID, const uint8_t
|
||||
}
|
||||
|
||||
auto p = make_shared<InitPacket>();
|
||||
p->cid = rCID;
|
||||
p->cmd = rCMD;
|
||||
p->cid = rCID;
|
||||
p->cmd = rCMD;
|
||||
p->bcnth = bcnth;
|
||||
p->bcntl = bcntl;
|
||||
p->data = dataBytes;
|
||||
p->data = dataBytes;
|
||||
|
||||
#ifdef DEBUG_STREAMS
|
||||
auto hPStream = getHostPacketStream().get();
|
||||
fprintf(hPStream, "\t\t<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>CID</th>\n"
|
||||
"\t\t\t\t\t<th>CMD</th>\n"
|
||||
"\t\t\t\t\t<th>BCNTH</th>\n"
|
||||
"\t\t\t\t\t<th>BCNTL</th>\n"
|
||||
"\t\t\t\t\t<th class=\"data\">DATA</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td>0x%08X</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">", p->cid, p->cmd, p->bcnth, p->bcntl);
|
||||
fprintf(hPStream,
|
||||
"\t\t<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>CID</th>\n"
|
||||
"\t\t\t\t\t<th>CMD</th>\n"
|
||||
"\t\t\t\t\t<th>BCNTH</th>\n"
|
||||
"\t\t\t\t\t<th>BCNTL</th>\n"
|
||||
"\t\t\t\t\t<th class=\"data\">DATA</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td>0x%08X</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">",
|
||||
p->cid, p->cmd, p->bcnth, p->bcntl);
|
||||
|
||||
for (auto elem : dataBytes)
|
||||
fprintf(hPStream, "%3u ", elem);
|
||||
|
||||
fprintf(hPStream, "</td>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>"
|
||||
"\t\t<br />");
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>"
|
||||
"\t\t<br />");
|
||||
#endif
|
||||
|
||||
bytesRead = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
shared_ptr<ContPacket> ContPacket::getPacket(const uint32_t rCID, const uint8_t rSeq)
|
||||
{
|
||||
shared_ptr<ContPacket> ContPacket::getPacket(const uint32_t rCID, const uint8_t rSeq) {
|
||||
static size_t readBytes = 0;
|
||||
static decltype(ContPacket::data) dataBytes;
|
||||
|
||||
vector<uint8_t> bytes{};
|
||||
auto p = make_shared<ContPacket>();
|
||||
|
||||
if (readBytes != dataBytes.size())
|
||||
{
|
||||
if (readBytes != dataBytes.size()) {
|
||||
dataBytes = {};
|
||||
bytes = readNonBlock(dataBytes.size());
|
||||
|
||||
@@ -140,48 +139,48 @@ shared_ptr<ContPacket> ContPacket::getPacket(const uint32_t rCID, const uint8_t
|
||||
|
||||
#ifdef DEBUG_STREAMS
|
||||
auto hPStream = getHostPacketStream().get();
|
||||
fprintf(hPStream, "\t\t<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>CID</th>\n"
|
||||
"\t\t\t\t\t<th>SEQ</th>\n"
|
||||
"\t\t\t\t\t<th class=\"data\">DATA</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td>0x%08X</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">", p->cid, p->seq);
|
||||
fprintf(hPStream,
|
||||
"\t\t<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>CID</th>\n"
|
||||
"\t\t\t\t\t<th>SEQ</th>\n"
|
||||
"\t\t\t\t\t<th class=\"data\">DATA</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td>0x%08X</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">",
|
||||
p->cid, p->seq);
|
||||
|
||||
for (auto elem : dataBytes)
|
||||
fprintf(hPStream, "%3u ", elem);
|
||||
|
||||
fprintf(hPStream, "</td>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>\n"
|
||||
"\t\t<br />");
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>\n"
|
||||
"\t\t<br />");
|
||||
#endif
|
||||
|
||||
readBytes = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
shared_ptr<Packet> Packet::getPacket()
|
||||
{
|
||||
shared_ptr<Packet> Packet::getPacket() {
|
||||
static size_t bytesRead = 0;
|
||||
vector<uint8_t> bytes{};
|
||||
|
||||
static uint32_t cid;
|
||||
static uint8_t b;
|
||||
static uint8_t b;
|
||||
shared_ptr<Packet> packet{};
|
||||
|
||||
switch (bytesRead)
|
||||
{
|
||||
switch (bytesRead) {
|
||||
case 0:
|
||||
bytes = readNonBlock(4);
|
||||
|
||||
|
||||
if (bytes.size() == 0)
|
||||
return {};
|
||||
|
||||
@@ -200,19 +199,16 @@ shared_ptr<Packet> Packet::getPacket()
|
||||
[[fallthrough]];
|
||||
|
||||
case 5:
|
||||
if (b & TYPE_MASK)
|
||||
{
|
||||
//Init packet
|
||||
if (b & TYPE_MASK) {
|
||||
// Init packet
|
||||
packet = InitPacket::getPacket(cid, b);
|
||||
|
||||
if (packet)
|
||||
bytesRead = 0;
|
||||
|
||||
return packet;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Cont packet
|
||||
} else {
|
||||
// Cont packet
|
||||
packet = ContPacket::getPacket(cid, b);
|
||||
|
||||
if (packet)
|
||||
@@ -225,97 +221,98 @@ shared_ptr<Packet> Packet::getPacket()
|
||||
}
|
||||
}
|
||||
|
||||
void Packet::writePacket()
|
||||
{
|
||||
memset(this->buf, 0, HID_RPT_SIZE);
|
||||
void Packet::writePacket() {
|
||||
memset(this->buf, 0, HID_RPT_SIZE);
|
||||
memcpy(this->buf, &cid, 4);
|
||||
}
|
||||
|
||||
void InitPacket::writePacket()
|
||||
{
|
||||
void InitPacket::writePacket() {
|
||||
Packet::writePacket();
|
||||
|
||||
#ifdef DEBUG_STREAMS
|
||||
auto devStream = getComDevStream().get();
|
||||
auto devStream = getComDevStream().get();
|
||||
#endif
|
||||
|
||||
memcpy(this->buf + 4, &cmd, 1);
|
||||
memcpy(this->buf + 5, &bcnth, 1);
|
||||
memcpy(this->buf + 6, &bcntl, 1);
|
||||
memcpy(this->buf + 4, &cmd, 1);
|
||||
memcpy(this->buf + 5, &bcnth, 1);
|
||||
memcpy(this->buf + 6, &bcntl, 1);
|
||||
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
|
||||
fwrite(this->buf, 1, sizeof(this->buf), devStream);
|
||||
fwrite(this->buf, 1, sizeof(this->buf), devStream);
|
||||
|
||||
auto dPStream = getDevPacketStream().get();
|
||||
fprintf(dPStream, "\t\t<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>CID</th>\n"
|
||||
"\t\t\t\t\t<th>CMD</th>\n"
|
||||
"\t\t\t\t\t<th>BCNTH</th>\n"
|
||||
"\t\t\t\t\t<th>BCNTL</th>\n"
|
||||
"\t\t\t\t\t<th class=\"data\">DATA</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td>0x%08X</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">", cid, cmd, bcnth, bcntl);
|
||||
fprintf(dPStream,
|
||||
"\t\t<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>CID</th>\n"
|
||||
"\t\t\t\t\t<th>CMD</th>\n"
|
||||
"\t\t\t\t\t<th>BCNTH</th>\n"
|
||||
"\t\t\t\t\t<th>BCNTL</th>\n"
|
||||
"\t\t\t\t\t<th class=\"data\">DATA</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td>0x%08X</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">",
|
||||
cid, cmd, bcnth, bcntl);
|
||||
|
||||
for (auto elem : data)
|
||||
fprintf(dPStream, "%3u ", elem);
|
||||
|
||||
fprintf(dPStream, "</td>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>"
|
||||
"\t\t<br />");
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>"
|
||||
"\t\t<br />");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ContPacket::writePacket()
|
||||
{
|
||||
void ContPacket::writePacket() {
|
||||
Packet::writePacket();
|
||||
|
||||
#ifdef DEBUG_STREAMS
|
||||
auto devStream = getComDevStream().get();
|
||||
auto devStream = getComDevStream().get();
|
||||
#endif
|
||||
|
||||
memcpy(this->buf + 4, &seq, 1);
|
||||
memcpy(this->buf + 4, &seq, 1);
|
||||
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
|
||||
fwrite(this->buf, HID_RPT_SIZE, 1, devStream);
|
||||
fwrite(this->buf, HID_RPT_SIZE, 1, devStream);
|
||||
|
||||
auto dPStream = getDevPacketStream().get();
|
||||
|
||||
fprintf(dPStream, "\t\t<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>CID</th>\n"
|
||||
"\t\t\t\t\t<th>SEQ</th>\n"
|
||||
"\t\t\t\t\t<th class=\"data\">DATA</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td>0x%08X</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">", cid, seq);
|
||||
fprintf(dPStream,
|
||||
"\t\t<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>CID</th>\n"
|
||||
"\t\t\t\t\t<th>SEQ</th>\n"
|
||||
"\t\t\t\t\t<th class=\"data\">DATA</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td>0x%08X</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">",
|
||||
cid, seq);
|
||||
|
||||
for (auto elem : data)
|
||||
fprintf(dPStream, "%3u ", elem);
|
||||
|
||||
fprintf(dPStream, "</td>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>\n"
|
||||
"\t\t<br />");
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>\n"
|
||||
"\t\t<br />");
|
||||
#endif
|
||||
}
|
||||
|
||||
63
Packet.hpp
63
Packet.hpp
@@ -17,48 +17,45 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "u2f.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include "u2f.hpp"
|
||||
|
||||
struct Packet
|
||||
{
|
||||
public:
|
||||
uint32_t cid;
|
||||
uint8_t buf[HID_RPT_SIZE];
|
||||
struct Packet {
|
||||
public:
|
||||
uint32_t cid;
|
||||
uint8_t buf[HID_RPT_SIZE];
|
||||
|
||||
protected:
|
||||
Packet() = default;
|
||||
virtual void writePacket();
|
||||
protected:
|
||||
Packet() = default;
|
||||
virtual void writePacket();
|
||||
|
||||
public:
|
||||
static std::shared_ptr<Packet> getPacket();
|
||||
virtual ~Packet() = default;
|
||||
public:
|
||||
static std::shared_ptr<Packet> getPacket();
|
||||
virtual ~Packet() = default;
|
||||
};
|
||||
|
||||
struct InitPacket : Packet
|
||||
{
|
||||
public:
|
||||
uint8_t cmd;
|
||||
uint8_t bcnth;
|
||||
uint8_t bcntl;
|
||||
std::array<uint8_t, HID_RPT_SIZE - 7> data{};
|
||||
struct InitPacket : Packet {
|
||||
public:
|
||||
uint8_t cmd;
|
||||
uint8_t bcnth;
|
||||
uint8_t bcntl;
|
||||
std::array<uint8_t, HID_RPT_SIZE - 7> data{};
|
||||
|
||||
public:
|
||||
InitPacket() = default;
|
||||
static std::shared_ptr<InitPacket> getPacket(const uint32_t rCID, const uint8_t rCMD);
|
||||
void writePacket() override;
|
||||
public:
|
||||
InitPacket() = default;
|
||||
static std::shared_ptr<InitPacket> getPacket(const uint32_t rCID, const uint8_t rCMD);
|
||||
void writePacket() override;
|
||||
};
|
||||
|
||||
struct ContPacket : Packet
|
||||
{
|
||||
public:
|
||||
uint8_t seq;
|
||||
std::array<uint8_t, HID_RPT_SIZE - 5> data{};
|
||||
struct ContPacket : Packet {
|
||||
public:
|
||||
uint8_t seq;
|
||||
std::array<uint8_t, HID_RPT_SIZE - 5> data{};
|
||||
|
||||
public:
|
||||
ContPacket() = default;
|
||||
static std::shared_ptr<ContPacket> getPacket(const uint32_t rCID, const uint8_t rSeq);
|
||||
void writePacket() override;
|
||||
public:
|
||||
ContPacket() = default;
|
||||
static std::shared_ptr<ContPacket> getPacket(const uint32_t rCID, const uint8_t rSeq);
|
||||
void writePacket() override;
|
||||
};
|
||||
|
||||
61
Readme.AttestationCertificateGeneration.md
Normal file
61
Readme.AttestationCertificateGeneration.md
Normal file
@@ -0,0 +1,61 @@
|
||||
From [Teensy U2F](https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp) line 292
|
||||
|
||||
Instructions to generate attestation certificate using open ssl
|
||||
[OpenSSL wiki](https://wiki.openssl.org/index.php/Command_Line_Elliptic_Curve_Operations)
|
||||
[Guy Rutenberg](https://www.guyrutenberg.com/2013/12/28/creating-self-signed-ecdsa-ssl-certificate-using-openssl/)
|
||||
P-256 (also secp256r1) EC key pair is W = dG (Note secp256k1 is Koblitz curve - not P256)
|
||||
d = private key is it 256 bits (32 bytes)
|
||||
G = generator point - it is part of the curve definition
|
||||
W = public key point - it is a (256, 256) bits - 64 bytes
|
||||
|
||||
1) Generate a key pair - the private key will be saved in PKCS8 format in ecprivkey.pem
|
||||
|
||||
`openssl ecparam -name prime256v1 -genkey -noout -out ecprivkey.pem`
|
||||
|
||||
2) Dump out the private key in hex format - it will be a 32 byte key
|
||||
|
||||
`openssl asn1parse -in ecprivkey.pem`
|
||||
|
||||
3) Compute the public key from the private key and the curve
|
||||
|
||||
`openssl ec -in ecprivkey.pem -pubout -out ecpubkey.pem`
|
||||
|
||||
4) Dump out the public key in hex format - it will be 66 byte - the first two bytes are 00 04,
|
||||
|
||||
`openssl ec -in ecprivkey.pem -pubout -text`
|
||||
|
||||
after that is the point W - 32 byte + 32 byte
|
||||
|
||||
5) Generate a self signed certificate
|
||||
|
||||
`openssl req -new -x509 -key ecprivkey.pem -out certificate.pem -days 3650`
|
||||
|
||||
For the Certificate name give a unique certificate name.
|
||||
|
||||
6) Display the certificate
|
||||
|
||||
`openssl x509 -in certificate.pem -text -noout`
|
||||
|
||||
7) Convert PEM certificate to DER
|
||||
|
||||
`openssl x509 -outform der -in certificate.pem -out certificate.der`
|
||||
|
||||
8) Generate a usable c-array for source code
|
||||
|
||||
`xxd --include certificate.der | sed -e '0,/{/d;/};/,$d'`
|
||||
|
||||
Copy output into appropriate array in 'Certificates.cpp', overwriting existing values
|
||||
|
||||
9) Find the public key
|
||||
|
||||
`openssl ec -in ecprivkey.pem -pubout -text -noout 2>/dev/null | sed -e '0,/priv:/d;/pub:/,$d' -e 's/\s//g;s/:/, /g' -e 's/^/\t/g;s/\s\+$//g' -e 's/\(\s\)/\10x/g'`
|
||||
|
||||
10) Find the private key
|
||||
|
||||
`openssl ec -in ecprivkey.pem -pubout -text -noout 2>/dev/null | sed -e '0,/pub:/d;/ASN1/,$d' | sed -e 's/\s//g;s/:/, /g' -e 's/^/\t/g;s/\s\+$//g' -e 's/\(\s\)/\10x/g'`
|
||||
|
||||
11)
|
||||
|
||||
Copy the arrays into the correct arrays in Certificates.cpp.
|
||||
|
||||
If any arrays have different lengths than shown in Certificates.hpp, update these too.
|
||||
@@ -1,45 +0,0 @@
|
||||
From https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp line 292
|
||||
|
||||
Instructions to generate attestation certificate using open ssl
|
||||
https://wiki.openssl.org/index.php/Command_Line_Elliptic_Curve_Operations
|
||||
https://www.guyrutenberg.com/2013/12/28/creating-self-signed-ecdsa-ssl-certificate-using-openssl/
|
||||
P-256 (also secp256r1) EC key pair is W = dG (Note secp256k1 is Koblitz curve - not P256)
|
||||
d = private key is it 256 bits (32 bytes)
|
||||
G = generator point - it is part of the curve definition
|
||||
W = public key point - it is a (256, 256) bits - 64 bytes
|
||||
1) generate a key pair - the private key will be saved in PKCS8 format in ecprivkey.pem
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out ecprivkey.pem
|
||||
2) dump out the private key in hex format - it will be a 32 byte key
|
||||
openssl asn1parse -in ecprivkey.pem
|
||||
3) compute the public key from the private key and the curve
|
||||
openssl ec -in ecprivkey.pem -pubout -out ecpubkey.pem
|
||||
4) dump out the public key in hex format - it will be 66 byte - the first two bytes are 00 04,
|
||||
openssl ec -in ecprivkey.pem -pubout -text
|
||||
after that is the point W - 32 byte + 32 byte
|
||||
5) generate a self signed certificate
|
||||
openssl req -new -x509 -key ecprivkey.pem -out server.pem -days 3650
|
||||
For the Certificate name give a unique certificate name. There is a 128 bit unique identification number burned into every
|
||||
Teensy chip - see http://cache.freescale.com/files/32bit/doc/data_sheet/K20P64M72SF1.pdf
|
||||
You can print out the number from your Teensy using this simple program given below
|
||||
6) Display the certificate
|
||||
openssl x509 -in server.pem -text -noout
|
||||
|
||||
7) Convert PEM certificate to DER
|
||||
openssl x509 -outform der -in server.pem -out certificate.der
|
||||
|
||||
8) Generate a usable c-array for source code
|
||||
xxd --include certificate.pem
|
||||
|
||||
Copy output into appropriate array in 'Certificates.cpp', overwriting existing values
|
||||
|
||||
9) Repeat steps 7 & 8 for public key and private key
|
||||
|
||||
So:
|
||||
`
|
||||
openssl asn1parse -in ecprivkey.pem 2>/dev/null | grep 'HEX DUMP' | perl -pe 's/^.*\[HEX DUMP\]:(.+)$/$1/' 2>/dev/null | xxd -r -p > privkey.der && xxd --include privkey.der
|
||||
|
||||
openssl ec -in ecprivkey.pem -pubout -text 2>/dev/null | perl -0777 -ne 'print /pub:.+ASN1/sg' 2>/dev/null | sed -e '/pub:/d;/ASN1/d' | perl -pe 's/^\s+(.+):?$/$1/gm' 2>/dev/null | perl -pe 's/\n//' 2>/dev/null | perl -pe 's/(.{2}):?/$1/g' 2>/dev/null | xxd -r -p > pubkey.der && xxd --include pubkey.der
|
||||
`
|
||||
and copy the arrays into the correct arrays in Certificates.cpp
|
||||
|
||||
If any arrays have different lengths than shown in Certificates.hpp, update these too.
|
||||
405
Readme.md
405
Readme.md
@@ -1,131 +1,232 @@
|
||||
# U2FDevice
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
All testing has been performed on a [Nexus 5X](https://en.wikipedia.org/wiki/Nexus_5X).
|
||||
|
||||
# Required materials
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
# 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
|
||||
|
||||

|
||||
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.
|
||||
1. Open the boot partition - it should look like so: <br />
|
||||
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).
|
||||
|
||||
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 />
|
||||
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 />
|
||||
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 />
|
||||
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 />
|
||||
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 />
|
||||
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.
|
||||
* 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.
|
||||
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).
|
||||
|
||||
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 />
|
||||
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 />
|
||||
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 />
|
||||
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 />
|
||||
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 />
|
||||
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 />
|
||||
|
||||
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 />
|
||||
|
||||
* 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.
|
||||
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).
|
||||
|
||||
## Setting up udev rules
|
||||
|
||||
@@ -133,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 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
|
||||
@@ -193,12 +299,16 @@ KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="18d1", ATTRS{idProduct
|
||||
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 />
|
||||
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 />
|
||||
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"`.
|
||||
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.
|
||||
|
||||
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 `
|
||||
|
||||
## Setting up to build
|
||||
@@ -207,78 +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`
|
||||
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`
|
||||
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`
|
||||
* enter the directory with `cd mbedtls`
|
||||
* build for cross-compile with `sudo make DESTDIR=/opt
|
||||
CXX=<cross-compiler-prefix>-g++ CC=<cross-compiler-prefix>-gcc install`,
|
||||
using the cross-compiler-prefix previously discussed in the [_Getting the
|
||||
kernel_](#Getting-the-kernel) phase.
|
||||
|
||||
## 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
|
||||
|
||||
## 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
|
||||
|
||||
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`
|
||||
* 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.
|
||||
* 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 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).
|
||||
* This solution is rather unwieldy - it requires a long boot time and must be shutdown (for now) with a command using SSH.
|
||||
* 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 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.
|
||||
|
||||
## To improve RNG (improve crypto security)
|
||||
|
||||
1. Install `rng-tools` with `sudo apt-get install rng-tools`
|
||||
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 change the Attestation certificate
|
||||
|
||||
This may be highly advisable, or inadvisable - I am currently unsure. <br />All registration requests use this private key, so likely advisable. <br/>However, you can be uniquely identified by having a unique attestation certificate.
|
||||
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.
|
||||
|
||||
See the `Readme.AttestationCertificateGeneration.txt`
|
||||
See the [`Readme.AttestationCertificateGeneration.txt`](Readme.AttestationCertificateGeneration.txt)
|
||||
|
||||
# Running the program
|
||||
|
||||
## To run
|
||||
|
||||
Run `sudo systemctl start U2FDevice.service`
|
||||
|
||||
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.
|
||||
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),
|
||||
[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.
|
||||
|
||||
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 systmectl enable U2FDevice.service`
|
||||
|
||||
## 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 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__
|
||||
* /tmp/comdev.txt
|
||||
* /tmp/comhost.txt
|
||||
* /data/local/tmp/comdev.txt
|
||||
* /data/local/tmp/comhost.txt
|
||||
|
||||
* __Packets__
|
||||
* /tmp/devPackets.html
|
||||
* /tmp/devAPDU.html
|
||||
* /tmp/hostPackets.html
|
||||
* /tmp/hostAPDU.html
|
||||
* /data/local/tmp/devPackets.html
|
||||
* /data/local/tmp/devAPDU.html
|
||||
* /data/local/tmp/hostPackets.html
|
||||
* /data/local/tmp/hostAPDU.html
|
||||
|
||||
6
Scripts/cpp-base64-CMakeLists.txt
Normal file
6
Scripts/cpp-base64-CMakeLists.txt
Normal 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
137
Scripts/u2f-kernel.patch
Normal 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
|
||||
+ }
|
||||
+};
|
||||
6
Scripts/uECC-CMakeLists.txt
Normal file
6
Scripts/uECC-CMakeLists.txt
Normal 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)
|
||||
@@ -21,27 +21,24 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using namespace std;
|
||||
|
||||
//Ripped from https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp
|
||||
void appendSignatureAsDER(vector<uint8_t> &response, const array<uint8_t, 64> &signature)
|
||||
{
|
||||
// Ripped from https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp
|
||||
void appendSignatureAsDER(vector<uint8_t>& response, const array<uint8_t, 64>& signature) {
|
||||
response.push_back(0x30); // Start of ASN.1 SEQUENCE
|
||||
response.push_back(68); //total length from (2 * (32 + 2)) to (2 * (33 + 2))
|
||||
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++)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
copy(offset, offset + 32, back_inserter(response)); // R or S value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
using Digest = std::array<uint8_t, 32>;
|
||||
using Digest = std::array<uint8_t, 32>;
|
||||
using Signature = std::array<uint8_t, 64>;
|
||||
|
||||
//Ripped from https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp
|
||||
void appendSignatureAsDER(std::vector<uint8_t> &response, const Signature &signature);
|
||||
// Ripped from https://github.com/pratikd650/Teensy_U2F/blob/master/Teensy_U2F.cpp
|
||||
void appendSignatureAsDER(std::vector<uint8_t>& response, const Signature& signature);
|
||||
|
||||
68
Storage.cpp
68
Storage.cpp
@@ -17,44 +17,49 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Storage.hpp"
|
||||
#include <exception>
|
||||
#include <sstream>
|
||||
#include "Base64.tpp"
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
std::string Storage::filename{};
|
||||
std::map<Storage::KeyHandle, Storage::AppParam> Storage::appParams{};
|
||||
std::map<Storage::KeyHandle, Storage::PrivKey> Storage::privKeys{};
|
||||
std::map<Storage::KeyHandle, Storage::PubKey> Storage::pubKeys{};
|
||||
std::map<Storage::KeyHandle, Storage::PrivKey> Storage::privKeys{};
|
||||
std::map<Storage::KeyHandle, Storage::PubKey> Storage::pubKeys{};
|
||||
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";
|
||||
ifstream file{ Storage::filename };
|
||||
|
||||
init(file);
|
||||
}
|
||||
|
||||
void Storage::init(std::istream& inputStream) {
|
||||
string line;
|
||||
size_t lineNumber = 0;
|
||||
|
||||
while (getline(file, line))
|
||||
{
|
||||
while (getline(inputStream, line)) {
|
||||
auto strLineNum = to_string(lineNumber);
|
||||
stringstream ss{ line };
|
||||
string keyHStr, appStr, privStr, pubStr, keyCStr;
|
||||
ss >> keyHStr >> appStr >> privStr >> pubStr >> keyCStr;
|
||||
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{ static_cast<Storage::KeyHandle>(strtoull(keyHStr.c_str(), &endP, 10)) };
|
||||
char* endP = nullptr;
|
||||
Storage::KeyHandle keyH{ static_cast<Storage::KeyHandle>(
|
||||
strtoull(keyHStr.c_str(), &endP, 10)) };
|
||||
|
||||
if (!endP)
|
||||
throw runtime_error{ "Invalid keyhandle format on line " + strLineNum };
|
||||
|
||||
|
||||
endP = nullptr;
|
||||
Storage::KeyCount keyC{ static_cast<Storage::KeyCount>(strtoull(keyCStr.c_str(), &endP, 10)) };
|
||||
Storage::KeyCount keyC{ static_cast<Storage::KeyCount>(
|
||||
strtoull(keyCStr.c_str(), &endP, 10)) };
|
||||
|
||||
if (!endP)
|
||||
throw runtime_error{ "Invalid key count format on line " + strLineNum };
|
||||
@@ -62,48 +67,49 @@ void Storage::init(const string &dirPrefix)
|
||||
Storage::AppParam appParam{};
|
||||
b64decode(appStr, appParam);
|
||||
|
||||
Storage::PrivKey privKey{};
|
||||
Storage::PrivKey privKey{};
|
||||
b64decode(privStr, privKey);
|
||||
|
||||
Storage::PubKey pubKey{};
|
||||
Storage::PubKey pubKey{};
|
||||
b64decode(pubStr, pubKey);
|
||||
|
||||
Storage::appParams[keyH] = appParam;
|
||||
Storage::privKeys[keyH] = privKey;
|
||||
Storage::pubKeys[keyH] = pubKey;
|
||||
Storage::privKeys[keyH] = privKey;
|
||||
Storage::pubKeys[keyH] = pubKey;
|
||||
Storage::keyCounts[keyH] = keyC;
|
||||
|
||||
lineNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::save()
|
||||
{
|
||||
void Storage::save() {
|
||||
ofstream file{ Storage::filename };
|
||||
Storage::save(file);
|
||||
}
|
||||
|
||||
for (auto &keypair : Storage::appParams)
|
||||
{
|
||||
const auto& keyID = keypair.first;
|
||||
void Storage::save(ostream& outputStream) {
|
||||
for (auto& keypair : Storage::appParams) {
|
||||
const auto& keyID = keypair.first;
|
||||
const auto& appParam = keypair.second;
|
||||
const auto& privKey = Storage::privKeys[keyID];
|
||||
const auto& pubKey = Storage::pubKeys[keyID];
|
||||
const auto& privKey = Storage::privKeys[keyID];
|
||||
const auto& pubKey = Storage::pubKeys[keyID];
|
||||
const auto& keyCount = Storage::keyCounts[keyID];
|
||||
|
||||
file << keyID;
|
||||
file << ' ';
|
||||
outputStream << keyID;
|
||||
outputStream << ' ';
|
||||
|
||||
string appPStr{};
|
||||
b64encode(appParam, appPStr);
|
||||
file << appPStr << ' ';
|
||||
outputStream << appPStr << ' ';
|
||||
|
||||
string privKStr{};
|
||||
b64encode(privKey, privKStr);
|
||||
file << privKStr << ' ';
|
||||
outputStream << privKStr << ' ';
|
||||
|
||||
string pubKStr{};
|
||||
b64encode(pubKey, pubKStr);
|
||||
file << pubKStr << ' ';
|
||||
outputStream << pubKStr << ' ';
|
||||
|
||||
file << keyCount << endl;
|
||||
outputStream << keyCount << endl;
|
||||
}
|
||||
}
|
||||
|
||||
37
Storage.hpp
37
Storage.hpp
@@ -17,30 +17,31 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class Storage
|
||||
{
|
||||
class Storage {
|
||||
public:
|
||||
using KeyHandle = uint32_t;
|
||||
using KeyCount = uint32_t;
|
||||
using AppParam = std::array<uint8_t, 32>;
|
||||
using PrivKey = std::array<uint8_t, 32>;
|
||||
using PubKey = std::array<uint8_t, 65>;
|
||||
using KeyCount = uint32_t;
|
||||
using AppParam = std::array<uint8_t, 32>;
|
||||
using PrivKey = std::array<uint8_t, 32>;
|
||||
using PubKey = std::array<uint8_t, 65>;
|
||||
|
||||
protected:
|
||||
Storage() = default;
|
||||
protected:
|
||||
Storage() = default;
|
||||
|
||||
static std::string filename;
|
||||
static std::string filename;
|
||||
|
||||
public:
|
||||
static void init(const std::string &dirPrefix = "");
|
||||
static void save();
|
||||
static std::map<KeyHandle, AppParam> appParams;
|
||||
static std::map<KeyHandle, PrivKey> privKeys;
|
||||
static std::map<KeyHandle, PubKey> pubKeys;
|
||||
static std::map<KeyHandle, KeyCount> keyCounts;
|
||||
public:
|
||||
static void init(const std::string& dirPrefix = "");
|
||||
static void init(std::istream& inputStream);
|
||||
static void save();
|
||||
static void save(std::ostream& outputStream);
|
||||
static std::map<KeyHandle, AppParam> appParams;
|
||||
static std::map<KeyHandle, PrivKey> privKeys;
|
||||
static std::map<KeyHandle, PubKey> pubKeys;
|
||||
static std::map<KeyHandle, KeyCount> keyCounts;
|
||||
};
|
||||
|
||||
363
Streams.cpp
363
Streams.cpp
@@ -17,162 +17,327 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Streams.hpp"
|
||||
#include <iostream>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include "Architecture.hpp"
|
||||
#include "IO.hpp"
|
||||
#include <android/log.h>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.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
|
||||
FILE* initHTML(FILE *fPtr, const string &title);
|
||||
void closeHTML(FILE *fPtr);
|
||||
|
||||
FILE* initHTML(FILE* fPtr, const string& title);
|
||||
|
||||
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
|
||||
|
||||
shared_ptr<int> getHostDescriptor()
|
||||
{
|
||||
static shared_ptr<int> 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" };
|
||||
|
||||
shared_ptr<int>& getHostDescriptor() {
|
||||
static shared_ptr<int> descriptor{
|
||||
#ifndef MANUAL_LIFETIME
|
||||
genHostDescriptor()
|
||||
#endif
|
||||
};
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
#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)
|
||||
clog << "Stream is unavailable" << endl;
|
||||
#endif
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
shared_ptr<FILE> getHostPacketStream()
|
||||
{
|
||||
static shared_ptr<FILE> stream{ initHTML(fopen(DEBUG_STREAMS "hostpackets.html", "wb"), "Host Packets"), [](FILE *f){
|
||||
clog << "Closing hostPackets stream" << endl;
|
||||
closeHTML(f);
|
||||
} };
|
||||
shared_ptr<FILE>& getHostPacketStream() {
|
||||
static shared_ptr<FILE> stream{
|
||||
#ifndef MANUAL_LIFETIME
|
||||
genHostPacketStream()
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef MANUAL_LIFETIME
|
||||
if (!stream)
|
||||
clog << "Stream is unavailable" << endl;
|
||||
#endif
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
shared_ptr<FILE> getHostAPDUStream()
|
||||
{
|
||||
static shared_ptr<FILE> stream{ initHTML(fopen(DEBUG_STREAMS "hostAPDU.html", "wb"), "Host APDU"), [](FILE *f){
|
||||
clog << "Closing host APDU stream" << endl;
|
||||
closeHTML(f);
|
||||
} };
|
||||
shared_ptr<FILE>& getHostAPDUStream() {
|
||||
static shared_ptr<FILE> stream{
|
||||
#ifndef MANUAL_LIFETIME
|
||||
genHostAPDUStream()
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef MANUAL_LIFETIME
|
||||
if (!stream)
|
||||
clog << "Stream is unavailable" << endl;
|
||||
#endif
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
shared_ptr<FILE> getComDevStream()
|
||||
{
|
||||
static shared_ptr<FILE> stream{ fopen(DEBUG_STREAMS "comdev.txt", "wb"), [](FILE *f){
|
||||
clog << "Closing comdev stream" << endl;
|
||||
fclose(f);
|
||||
} };
|
||||
shared_ptr<FILE>& getComDevStream() {
|
||||
static shared_ptr<FILE> stream{
|
||||
#ifndef MANUAL_LIFETIME
|
||||
genComDevStream()
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef MANUAL_LIFETIME
|
||||
if (!stream)
|
||||
clog << "Stream is unavailable" << endl;
|
||||
#endif
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
shared_ptr<FILE> getDevPacketStream()
|
||||
{
|
||||
static shared_ptr<FILE> stream{ initHTML(fopen(DEBUG_STREAMS "devpackets.html", "wb"), "Dev Packets"), [](FILE *f){
|
||||
clog << "Closing devPackets stream" << endl;
|
||||
closeHTML(f);
|
||||
} };
|
||||
shared_ptr<FILE>& getDevPacketStream() {
|
||||
static shared_ptr<FILE> stream{
|
||||
#ifndef MANUAL_LIFETIME
|
||||
genDevPacketStream()
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef MANUAL_LIFETIME
|
||||
if (!stream)
|
||||
clog << "Stream is unavailable" << endl;
|
||||
#endif
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
shared_ptr<FILE> getDevAPDUStream()
|
||||
{
|
||||
static shared_ptr<FILE> stream{ initHTML(fopen(DEBUG_STREAMS "devAPDU.html", "wb"), "Dev APDU"), [](FILE *f){
|
||||
clog << "Closing dev APDU stream" << endl;
|
||||
closeHTML(f);
|
||||
} };
|
||||
shared_ptr<FILE>& getDevAPDUStream() {
|
||||
static shared_ptr<FILE> stream{
|
||||
#ifndef MANUAL_LIFETIME
|
||||
genDevAPDUStream()
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef MANUAL_LIFETIME
|
||||
if (!stream)
|
||||
clog << "Stream is unavailable" << endl;
|
||||
#endif
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
FILE* initHTML(FILE *fPtr, const string &title)
|
||||
{
|
||||
fprintf(fPtr, "<html>\n"
|
||||
"\t<head>\n"
|
||||
"\t\t<title>%s</title>\n"
|
||||
"\t\t<style>\n"
|
||||
"\t\t\ttable {\n"
|
||||
"\t\t\t\tdisplay: table;\n"
|
||||
"\t\t\t\twidth: 100%%;\n"
|
||||
"\t\t\t\tborder-collapse: collapse;\n"
|
||||
"\t\t\t\tbox-sizing: border-box;\n"
|
||||
"\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\tth.data {\n"
|
||||
"\t\t\t\ttext-align: left;\n"
|
||||
"\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\ttd {\n"
|
||||
"\t\t\t\tfont-family: \"Courier New\", Courier, monospace;\n"
|
||||
"\t\t\t\twhite-space: pre;\n"
|
||||
"\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\ttd.data {\n"
|
||||
"\t\t\t\toverflow: hidden;\n"
|
||||
"\t\t\t\ttext-overflow: ellipsis;\n"
|
||||
"\t\t\t\tmax-width:1px;\n"
|
||||
"\t\t\t\twidth:100%%;\n"
|
||||
"\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\ttd.data:hover {\n"
|
||||
"\t\t\t\twhite-space: pre-wrap;\n"
|
||||
"\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\ttable, th, td {\n"
|
||||
"\t\t\t\tborder: 1px solid black;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t</style>\n"
|
||||
"\t</head>\n"
|
||||
"\n"
|
||||
"\t<body>", title.c_str());
|
||||
FILE* initHTML(FILE* fPtr, const string& title) {
|
||||
fprintf(fPtr,
|
||||
"<html>\n"
|
||||
"\t<head>\n"
|
||||
"\t\t<title>%s</title>\n"
|
||||
"\t\t<style>\n"
|
||||
"\t\t\ttable {\n"
|
||||
"\t\t\t\tdisplay: table;\n"
|
||||
"\t\t\t\twidth: 100%%;\n"
|
||||
"\t\t\t\tborder-collapse: collapse;\n"
|
||||
"\t\t\t\tbox-sizing: border-box;\n"
|
||||
"\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\tth.data {\n"
|
||||
"\t\t\t\ttext-align: left;\n"
|
||||
"\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\ttd {\n"
|
||||
"\t\t\t\tfont-family: \"Courier New\", Courier, monospace;\n"
|
||||
"\t\t\t\twhite-space: pre;\n"
|
||||
"\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\ttd.data {\n"
|
||||
"\t\t\t\toverflow: hidden;\n"
|
||||
"\t\t\t\ttext-overflow: ellipsis;\n"
|
||||
"\t\t\t\tmax-width:1px;\n"
|
||||
"\t\t\t\twidth:100%%;\n"
|
||||
"\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\ttd.data:hover {\n"
|
||||
"\t\t\t\twhite-space: pre-wrap;\n"
|
||||
"\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\ttable, th, td {\n"
|
||||
"\t\t\t\tborder: 1px solid black;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t</style>\n"
|
||||
"\t</head>\n"
|
||||
"\n"
|
||||
"\t<body>",
|
||||
title.c_str());
|
||||
|
||||
return fPtr;
|
||||
}
|
||||
|
||||
void closeHTML(FILE *fPtr)
|
||||
{
|
||||
void closeHTML(FILE* fPtr) {
|
||||
fprintf(fPtr, "\t</body>\n"
|
||||
"</html>");
|
||||
fclose(fPtr);
|
||||
"</html>");
|
||||
int successCode = fclose(fPtr);
|
||||
|
||||
if (successCode != 0)
|
||||
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
|
||||
|
||||
21
Streams.hpp
21
Streams.hpp
@@ -17,17 +17,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "Architecture.hpp"
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include "Architecture.hpp"
|
||||
|
||||
std::shared_ptr<int> getHostDescriptor();
|
||||
std::shared_ptr<int>& getHostDescriptor();
|
||||
|
||||
#ifdef MANUAL_LIFETIME
|
||||
void initStreams();
|
||||
void closeStreams();
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_STREAMS
|
||||
std::shared_ptr<FILE> getComHostStream();
|
||||
std::shared_ptr<FILE> getHostPacketStream();
|
||||
std::shared_ptr<FILE> getHostAPDUStream();
|
||||
std::shared_ptr<FILE> getComDevStream();
|
||||
std::shared_ptr<FILE> getDevPacketStream();
|
||||
std::shared_ptr<FILE> getDevAPDUStream();
|
||||
std::shared_ptr<FILE>& getComHostStream();
|
||||
std::shared_ptr<FILE>& getHostPacketStream();
|
||||
std::shared_ptr<FILE>& getHostAPDUStream();
|
||||
std::shared_ptr<FILE>& getComDevStream();
|
||||
std::shared_ptr<FILE>& getDevPacketStream();
|
||||
std::shared_ptr<FILE>& getDevAPDUStream();
|
||||
#endif
|
||||
|
||||
@@ -17,11 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Architecture.hpp"
|
||||
#include <iostream>
|
||||
#include "Storage.hpp"
|
||||
#include "Controller.hpp"
|
||||
#include "LED.hpp"
|
||||
#include <signal.h>
|
||||
#include "Storage.hpp"
|
||||
#include <csignal>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std;
|
||||
@@ -31,43 +31,30 @@ void signalCallback(int signum);
|
||||
volatile bool contProc = true;
|
||||
|
||||
bool initialiseLights(const string& prog) {
|
||||
try
|
||||
{
|
||||
try {
|
||||
disableACTTrigger(true);
|
||||
enableACTLED(false);
|
||||
}
|
||||
catch (runtime_error &e)
|
||||
{
|
||||
} catch (runtime_error& e) {
|
||||
cerr << e.what() << endl;
|
||||
|
||||
if (getuid() != 0)
|
||||
cerr << "Try running as root, using \'sudo " << prog << "\'" << endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int handleTransactions(const string& prog, const string& privKeyDir)
|
||||
{
|
||||
int handleTransactions(const string& prog, const string& privKeyDir) {
|
||||
signal(SIGINT, signalCallback);
|
||||
signal(SIGTERM, signalCallback);
|
||||
Storage::init(privKeyDir);
|
||||
Controller ch{ 0xF1D00000 };
|
||||
|
||||
while (contProc)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (contProc) {
|
||||
try {
|
||||
ch.handleTransaction();
|
||||
}
|
||||
catch (const runtime_error &e)
|
||||
{
|
||||
} catch (const runtime_error& e) {
|
||||
cerr << e.what() << endl;
|
||||
|
||||
if (getuid() != 0)
|
||||
cerr << "Try running as root, using \'sudo " << prog << "\'" << endl;
|
||||
|
||||
raise(SIGINT);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@@ -79,26 +66,19 @@ int handleTransactions(const string& prog, const string& privKeyDir)
|
||||
}
|
||||
|
||||
bool deinitialiseLights(const string& prog) {
|
||||
try
|
||||
{
|
||||
try {
|
||||
disableACTTrigger(false);
|
||||
enableACTLED(true);
|
||||
}
|
||||
catch (runtime_error &e)
|
||||
{
|
||||
} catch (runtime_error& e) {
|
||||
cerr << e.what() << endl;
|
||||
|
||||
if (getuid() != 0)
|
||||
cerr << "Try running as root, using \'sudo " << prog << "\'" << endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void signalCallback([[maybe_unused]] int signum)
|
||||
{
|
||||
void signalCallback([[maybe_unused]] int signum) {
|
||||
contProc = false;
|
||||
clog << "\nClosing" << endl;
|
||||
}
|
||||
|
||||
@@ -24,4 +24,3 @@ extern volatile bool contProc;
|
||||
bool initialiseLights(const std::string& prog);
|
||||
bool deinitialiseLights(const std::string& prog);
|
||||
int handleTransactions(const std::string& prog, const std::string& privKeyDir);
|
||||
|
||||
|
||||
128
U2FMessage.cpp
128
U2FMessage.cpp
@@ -17,37 +17,34 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "U2FMessage.hpp"
|
||||
#include "IO.hpp"
|
||||
#include "Packet.hpp"
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include "Streams.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include "IO.hpp"
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
shared_ptr<U2FMessage> U2FMessage::readNonBlock()
|
||||
{
|
||||
const static size_t startSeq = (size_t)-1ull;
|
||||
static size_t currSeq = startSeq;
|
||||
static uint16_t messageSize;
|
||||
static uint32_t cid;
|
||||
static uint8_t cmd;
|
||||
shared_ptr<U2FMessage> U2FMessage::readNonBlock() {
|
||||
const static size_t startSeq = (size_t)-1ull;
|
||||
static size_t currSeq = startSeq;
|
||||
static uint16_t messageSize;
|
||||
static uint32_t cid;
|
||||
static uint8_t cmd;
|
||||
static vector<uint8_t> dataBytes;
|
||||
|
||||
shared_ptr<Packet> p{};
|
||||
|
||||
if (currSeq == startSeq)
|
||||
{
|
||||
if (currSeq == startSeq) {
|
||||
cid = 0;
|
||||
cmd = 0;
|
||||
messageSize = 0;
|
||||
dataBytes = {};
|
||||
|
||||
|
||||
shared_ptr<InitPacket> initPack{};
|
||||
do
|
||||
{
|
||||
do {
|
||||
p = Packet::getPacket();
|
||||
|
||||
if (!p)
|
||||
@@ -59,43 +56,46 @@ shared_ptr<U2FMessage> U2FMessage::readNonBlock()
|
||||
if (!initPack)
|
||||
cerr << "Spurious cont. packet" << endl;
|
||||
#endif
|
||||
} while (!initPack); //Spurious cont. packet - spec states ignore
|
||||
} while (!initPack); // Spurious cont. packet - spec states ignore
|
||||
|
||||
messageSize = ((static_cast<uint16_t>(initPack->bcnth) << 8u) + initPack->bcntl);
|
||||
const uint16_t copyByteCount = min(static_cast<uint16_t>(initPack->data.size()), messageSize);
|
||||
const uint16_t copyByteCount =
|
||||
min(static_cast<uint16_t>(initPack->data.size()), messageSize);
|
||||
|
||||
cid = initPack->cid;
|
||||
cmd = initPack->cmd;
|
||||
|
||||
copy(initPack->data.begin(), initPack->data.begin() + copyByteCount, back_inserter(dataBytes));
|
||||
|
||||
copy(initPack->data.begin(), initPack->data.begin() + copyByteCount,
|
||||
back_inserter(dataBytes));
|
||||
currSeq = 0;
|
||||
}
|
||||
|
||||
while (messageSize > dataBytes.size() && static_cast<bool>(p = Packet::getPacket())) //While there is a packet
|
||||
while (messageSize > dataBytes.size() &&
|
||||
static_cast<bool>(p = Packet::getPacket())) // While there is a packet
|
||||
{
|
||||
auto contPack = dynamic_pointer_cast<ContPacket>(p);
|
||||
|
||||
if (!contPack) //Spurious init. packet
|
||||
if (!contPack) // Spurious init. packet
|
||||
{
|
||||
#ifdef DEBUG_MSGS
|
||||
cerr << "Spurious init. packet" << endl;
|
||||
#endif
|
||||
currSeq = startSeq; //Reset
|
||||
currSeq = startSeq; // Reset
|
||||
return {};
|
||||
}
|
||||
|
||||
if (contPack->cid != cid) //Cont. packet of different CID
|
||||
if (contPack->cid != cid) // Cont. packet of different CID
|
||||
{
|
||||
#ifdef DEBUG_MSGS
|
||||
cerr << "Invalid CID: was handling channel 0x" << hex << cid << " and received packet from channel 0x" << contPack->cid << dec << endl;
|
||||
cerr << "Invalid CID: was handling channel 0x" << hex << cid
|
||||
<< " and received packet from channel 0x" << contPack->cid << dec << endl;
|
||||
#endif
|
||||
U2FMessage::error(contPack->cid, ERR_CHANNEL_BUSY);
|
||||
currSeq = startSeq;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (contPack->seq != currSeq)
|
||||
{
|
||||
if (contPack->seq != currSeq) {
|
||||
#ifdef DEBUG_MSGS
|
||||
cerr << "Invalid packet seq. value" << endl;
|
||||
#endif
|
||||
@@ -105,15 +105,18 @@ shared_ptr<U2FMessage> U2FMessage::readNonBlock()
|
||||
}
|
||||
|
||||
const uint16_t remainingBytes = messageSize - dataBytes.size();
|
||||
const uint16_t copyBytes = min(static_cast<uint16_t>(contPack->data.size()), remainingBytes);
|
||||
const uint16_t copyBytes =
|
||||
min(static_cast<uint16_t>(contPack->data.size()), remainingBytes);
|
||||
|
||||
dataBytes.insert(dataBytes.end(), contPack->data.begin(), contPack->data.begin() + copyBytes);
|
||||
dataBytes.insert(dataBytes.end(), contPack->data.begin(),
|
||||
contPack->data.begin() + copyBytes);
|
||||
currSeq++;
|
||||
}
|
||||
|
||||
if (messageSize != dataBytes.size()) {
|
||||
#ifdef DEBUG_MSGS
|
||||
cerr << "Invalid message size: " << messageSize << " when received " << dataBytes.size() << endl;
|
||||
cerr << "Invalid message size: " << messageSize << " when received " << dataBytes.size()
|
||||
<< endl;
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
@@ -125,8 +128,7 @@ shared_ptr<U2FMessage> U2FMessage::readNonBlock()
|
||||
return message;
|
||||
}
|
||||
|
||||
void U2FMessage::write()
|
||||
{
|
||||
void U2FMessage::write() {
|
||||
const uint16_t bytesToWrite = this->data.size();
|
||||
uint16_t bytesWritten = 0;
|
||||
|
||||
@@ -141,7 +143,8 @@ void U2FMessage::write()
|
||||
p.bcntl = bcntl;
|
||||
|
||||
{
|
||||
uint16_t initialByteCount = min(static_cast<uint16_t>(p.data.size()), static_cast<uint16_t>(bytesToWrite - bytesWritten));
|
||||
uint16_t initialByteCount = min(static_cast<uint16_t>(p.data.size()),
|
||||
static_cast<uint16_t>(bytesToWrite - bytesWritten));
|
||||
copy(data.begin(), data.begin() + initialByteCount, p.data.begin());
|
||||
bytesWritten += initialByteCount;
|
||||
}
|
||||
@@ -151,58 +154,57 @@ void U2FMessage::write()
|
||||
|
||||
uint8_t seq = 0;
|
||||
|
||||
while (bytesWritten != bytesToWrite)
|
||||
{
|
||||
while (bytesWritten != bytesToWrite) {
|
||||
ContPacket p{};
|
||||
p.cid = cid;
|
||||
p.seq = seq;
|
||||
uint16_t newByteCount = min(static_cast<uint16_t>(p.data.size()), static_cast<uint16_t>(bytesToWrite - bytesWritten));
|
||||
copy(data.begin() + bytesWritten, data.begin() + bytesWritten + newByteCount, p.data.begin());
|
||||
uint16_t newByteCount = min(static_cast<uint16_t>(p.data.size()),
|
||||
static_cast<uint16_t>(bytesToWrite - bytesWritten));
|
||||
copy(data.begin() + bytesWritten, data.begin() + bytesWritten + newByteCount,
|
||||
p.data.begin());
|
||||
p.writePacket();
|
||||
seq++;
|
||||
bytesWritten += newByteCount;
|
||||
}
|
||||
|
||||
if (cmd == U2FHID_MSG)
|
||||
{
|
||||
if (cmd == U2FHID_MSG) {
|
||||
#ifdef DEBUG_STREAMS
|
||||
auto dAS = getDevAPDUStream().get();
|
||||
|
||||
|
||||
fprintf(dAS, "<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>DATA</th>\n"
|
||||
"\t\t\t\t\t<th>ERR</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">");
|
||||
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>DATA</th>\n"
|
||||
"\t\t\t\t\t<th>ERR</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">");
|
||||
|
||||
for (size_t i = 0; i < data.size() - 2; i++)
|
||||
fprintf(dAS, "%3u ", data[i]);
|
||||
#endif
|
||||
|
||||
|
||||
uint16_t err = data[data.size() - 2] << 8;
|
||||
err |= data.back();
|
||||
|
||||
|
||||
#ifdef DEBUG_STREAMS
|
||||
fprintf(dAS, "</td>\n"
|
||||
"\t\t\t\t\t<td>0x%04X</td>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>\n"
|
||||
"\t\t<br />", err);
|
||||
fprintf(dAS,
|
||||
"</td>\n"
|
||||
"\t\t\t\t\t<td>0x%04X</td>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>\n"
|
||||
"\t\t<br />",
|
||||
err);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
U2FMessage::U2FMessage(const uint32_t nCID, const uint8_t nCMD)
|
||||
: cid{ nCID }, cmd{ nCMD }
|
||||
{}
|
||||
U2FMessage::U2FMessage(const uint32_t nCID, const uint8_t nCMD) : cid{ nCID }, cmd{ nCMD } {}
|
||||
|
||||
void U2FMessage::error(const uint32_t tCID, const uint8_t tErr)
|
||||
{
|
||||
void U2FMessage::error(const uint32_t tCID, const uint8_t tErr) {
|
||||
U2FMessage msg{};
|
||||
msg.cid = tCID;
|
||||
msg.cmd = U2FHID_ERROR;
|
||||
|
||||
@@ -18,21 +18,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
struct U2FMessage
|
||||
{
|
||||
public:
|
||||
uint32_t cid;
|
||||
uint8_t cmd;
|
||||
std::vector<uint8_t> data;
|
||||
struct U2FMessage {
|
||||
public:
|
||||
uint32_t cid;
|
||||
uint8_t cmd;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
public:
|
||||
U2FMessage() = default;
|
||||
U2FMessage(const uint32_t nCID, const uint8_t nCMD);
|
||||
static std::shared_ptr<U2FMessage> readNonBlock();
|
||||
void write();
|
||||
static void error(const uint32_t tCID, const uint8_t tErr);
|
||||
public:
|
||||
U2FMessage() = default;
|
||||
U2FMessage(const uint32_t nCID, const uint8_t nCMD);
|
||||
static std::shared_ptr<U2FMessage> readNonBlock();
|
||||
void write();
|
||||
static void error(const uint32_t tCID, const uint8_t tErr);
|
||||
};
|
||||
|
||||
@@ -17,33 +17,29 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "U2F_Authenticate_APDU.hpp"
|
||||
#include "Field.hpp"
|
||||
#include "U2FMessage.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include "Field.tpp"
|
||||
#include "APDU.hpp"
|
||||
#include <iostream>
|
||||
#include "Field.hpp"
|
||||
#include "Field.tpp"
|
||||
#include "Signature.hpp"
|
||||
#include "U2FMessage.hpp"
|
||||
#include "micro-ecc/uECC.h"
|
||||
#include "u2f.hpp"
|
||||
#include <iostream>
|
||||
#include <mbedtls/sha256.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
U2F_Authenticate_APDU::U2F_Authenticate_APDU(const U2F_Msg_CMD &msg, const vector<uint8_t> &data)
|
||||
: U2F_Msg_CMD{ msg }
|
||||
{
|
||||
if (p2 != 0)
|
||||
{
|
||||
//Invalid U2F (APDU) parameter detected
|
||||
U2F_Authenticate_APDU::U2F_Authenticate_APDU(const U2F_Msg_CMD& msg, const vector<uint8_t>& data)
|
||||
: U2F_Msg_CMD{ msg } {
|
||||
if (p2 != 0) {
|
||||
// Invalid U2F (APDU) parameter detected
|
||||
throw APDU_STATUS::SW_CONDITIONS_NOT_SATISFIED;
|
||||
}
|
||||
else if (data.size() < 66)
|
||||
{
|
||||
//Invalid authentication request
|
||||
} else if (data.size() < 66) {
|
||||
// Invalid authentication request
|
||||
throw APDU_STATUS::SW_WRONG_LENGTH;
|
||||
}
|
||||
|
||||
copy(data.begin() + 0, data.begin() + 32, challengeP.begin());
|
||||
copy(data.begin() + 0, data.begin() + 32, challengeP.begin());
|
||||
copy(data.begin() + 32, data.begin() + 64, appParam.begin());
|
||||
|
||||
uint8_t keyHLen = data[64];
|
||||
@@ -51,50 +47,40 @@ U2F_Authenticate_APDU::U2F_Authenticate_APDU(const U2F_Msg_CMD &msg, const vecto
|
||||
copy(data.begin() + 65, data.begin() + 65 + keyHLen, back_inserter(keyH));
|
||||
}
|
||||
|
||||
void U2F_Authenticate_APDU::respond(const uint32_t channelID) const
|
||||
{
|
||||
if (keyH.size() != sizeof(Storage::KeyHandle))
|
||||
{
|
||||
//Respond with error code - key handle is of wrong size
|
||||
void U2F_Authenticate_APDU::respond(const uint32_t channelID, bool hasAuthorisation) const {
|
||||
if (keyH.size() != sizeof(Storage::KeyHandle)) {
|
||||
// Respond with error code - key handle is of wrong size
|
||||
cerr << "Invalid key handle length" << endl;
|
||||
this->error(channelID, APDU_STATUS::SW_WRONG_DATA);
|
||||
return;
|
||||
}
|
||||
|
||||
auto keyHB = *reinterpret_cast<const Storage::KeyHandle*>(keyH.data());
|
||||
|
||||
if (Storage::appParams.find(keyHB) == Storage::appParams.end())
|
||||
{
|
||||
//Respond with error code - key handle doesn't exist in storage
|
||||
|
||||
if (Storage::appParams.find(keyHB) == Storage::appParams.end()) {
|
||||
// Respond with error code - key handle doesn't exist in storage
|
||||
cerr << "Invalid key handle" << endl;
|
||||
this->error(channelID, SW_WRONG_DATA);
|
||||
this->error(channelID, APDU_STATUS::SW_WRONG_DATA);
|
||||
return;
|
||||
}
|
||||
|
||||
auto appMatches = (Storage::appParams.at(keyHB) == appParam);
|
||||
|
||||
U2FMessage msg{};
|
||||
msg.cid = channelID;
|
||||
msg.cmd = U2FHID_MSG;
|
||||
if (!appMatches) {
|
||||
this->error(channelID, APDU_STATUS::SW_WRONG_DATA);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &response = msg.data;
|
||||
APDU_STATUS statusCode = APDU_STATUS::SW_NO_ERROR;
|
||||
uint8_t presence;
|
||||
|
||||
switch (p1)
|
||||
{
|
||||
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();
|
||||
this->error(channelID, APDU_STATUS::SW_CONDITIONS_NOT_SATISFIED);
|
||||
return;
|
||||
case ControlCode::EnforcePresenceSign:
|
||||
//Continue processing
|
||||
// Continue processing
|
||||
case ControlCode::DontEnforcePresenceSign:
|
||||
//Continue processing
|
||||
// Continue processing
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -103,11 +89,18 @@ void U2F_Authenticate_APDU::respond(const uint32_t channelID) const
|
||||
return;
|
||||
}
|
||||
|
||||
U2FMessage msg{};
|
||||
msg.cid = channelID;
|
||||
msg.cmd = U2FHID_MSG;
|
||||
|
||||
auto& response = msg.data;
|
||||
APDU_STATUS statusCode = APDU_STATUS::SW_NO_ERROR;
|
||||
|
||||
const auto& privKey = Storage::privKeys[keyHB];
|
||||
auto& keyCount = Storage::keyCounts[keyHB];
|
||||
auto& keyCount = Storage::keyCounts[keyHB];
|
||||
keyCount++;
|
||||
|
||||
response.push_back(0x01);
|
||||
response.push_back(hasAuthorisation ? 1u : 0u);
|
||||
response.insert(response.end(), FIELD_BE(keyCount));
|
||||
|
||||
Digest digest;
|
||||
@@ -117,8 +110,9 @@ void U2F_Authenticate_APDU::respond(const uint32_t channelID) const
|
||||
mbedtls_sha256_init(&shaContext);
|
||||
mbedtls_sha256_starts(&shaContext, 0);
|
||||
|
||||
mbedtls_sha256_update(&shaContext, reinterpret_cast<const uint8_t *>(appParam.data()), sizeof(appParam));
|
||||
uint8_t userPresence{ 1u };
|
||||
mbedtls_sha256_update(&shaContext, reinterpret_cast<const uint8_t*>(appParam.data()),
|
||||
sizeof(appParam));
|
||||
uint8_t userPresence = hasAuthorisation ? 1u : 0u;
|
||||
mbedtls_sha256_update(&shaContext, &userPresence, 1);
|
||||
const auto beCounter = beEncode(keyCount);
|
||||
mbedtls_sha256_update(&shaContext, beCounter.data(), beCounter.size());
|
||||
@@ -136,3 +130,7 @@ void U2F_Authenticate_APDU::respond(const uint32_t channelID) const
|
||||
|
||||
msg.write();
|
||||
}
|
||||
|
||||
bool U2F_Authenticate_APDU::requiresAuthorisation() const {
|
||||
return p1 == ControlCode::EnforcePresenceSign;
|
||||
}
|
||||
|
||||
@@ -17,23 +17,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "U2F_Msg_CMD.hpp"
|
||||
#include "Storage.hpp"
|
||||
#include "U2F_Msg_CMD.hpp"
|
||||
|
||||
struct U2F_Authenticate_APDU : U2F_Msg_CMD
|
||||
{
|
||||
struct U2F_Authenticate_APDU : U2F_Msg_CMD {
|
||||
uint8_t controlByte;
|
||||
std::array<uint8_t, 32> challengeP;
|
||||
Storage::AppParam appParam;
|
||||
std::vector<uint8_t> keyH;
|
||||
Storage::AppParam appParam;
|
||||
std::vector<uint8_t> keyH;
|
||||
|
||||
public:
|
||||
U2F_Authenticate_APDU(const U2F_Msg_CMD &msg, const std::vector<uint8_t> &data);
|
||||
public:
|
||||
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;
|
||||
|
||||
enum ControlCode
|
||||
{
|
||||
enum ControlCode {
|
||||
CheckOnly = 0x07,
|
||||
EnforcePresenceSign = 0x03,
|
||||
DontEnforcePresenceSign = 0x08
|
||||
|
||||
25
U2F_CMD.cpp
25
U2F_CMD.cpp
@@ -17,19 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "U2F_CMD.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include "U2F_Msg_CMD.hpp"
|
||||
#include "U2F_Init_CMD.hpp"
|
||||
#include "U2F_Msg_CMD.hpp"
|
||||
#include "U2F_Ping_CMD.hpp"
|
||||
#include "u2f.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
shared_ptr<U2F_CMD> U2F_CMD::get(const shared_ptr<U2FMessage> uMsg)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (uMsg->cmd)
|
||||
{
|
||||
shared_ptr<U2F_CMD> U2F_CMD::get(const U2FMessage& uMsg) {
|
||||
try {
|
||||
switch (uMsg.cmd) {
|
||||
case U2FHID_PING:
|
||||
return make_shared<U2F_Ping_CMD>(uMsg);
|
||||
case U2FHID_MSG:
|
||||
@@ -37,13 +34,15 @@ shared_ptr<U2F_CMD> U2F_CMD::get(const shared_ptr<U2FMessage> uMsg)
|
||||
case U2FHID_INIT:
|
||||
return make_shared<U2F_Init_CMD>(uMsg);
|
||||
default:
|
||||
U2FMessage::error(uMsg->cid, ERR_INVALID_CMD);
|
||||
U2FMessage::error(uMsg.cid, ERR_INVALID_CMD);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
catch (runtime_error& ignored)
|
||||
{
|
||||
U2FMessage::error(uMsg->cid, ERR_OTHER);
|
||||
} catch (runtime_error& ignored) {
|
||||
U2FMessage::error(uMsg.cid, ERR_OTHER);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool U2F_CMD::requiresAuthorisation() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
20
U2F_CMD.hpp
20
U2F_CMD.hpp
@@ -17,16 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "U2FMessage.hpp"
|
||||
#include <memory>
|
||||
|
||||
struct U2F_CMD
|
||||
{
|
||||
protected:
|
||||
U2F_CMD() = default;
|
||||
struct U2F_CMD {
|
||||
protected:
|
||||
U2F_CMD() = default;
|
||||
|
||||
public:
|
||||
virtual ~U2F_CMD() = default;
|
||||
static std::shared_ptr<U2F_CMD> get(const std::shared_ptr<U2FMessage> uMsg);
|
||||
virtual void respond(const uint32_t channelID) const = 0;
|
||||
}; //For polymorphic type casting
|
||||
public:
|
||||
virtual ~U2F_CMD() = default;
|
||||
virtual bool requiresAuthorisation() const;
|
||||
static std::shared_ptr<U2F_CMD> get(const U2FMessage& uMsg);
|
||||
virtual void respond(const uint32_t channelID, bool hasAuthorisation) const = 0;
|
||||
}; // For polymorphic type casting
|
||||
|
||||
@@ -17,43 +17,38 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "U2F_Init_CMD.hpp"
|
||||
#include <stdexcept>
|
||||
#include "u2f.hpp"
|
||||
#include "Field.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
U2F_Init_CMD::U2F_Init_CMD(const shared_ptr<U2FMessage> uMsg)
|
||||
{
|
||||
if (uMsg->cmd != U2FHID_INIT)
|
||||
U2F_Init_CMD::U2F_Init_CMD(const U2FMessage& uMsg) {
|
||||
if (uMsg.cmd != U2FHID_INIT)
|
||||
throw runtime_error{ "Failed to get U2F Init message" };
|
||||
else if (uMsg->cid != CID_BROADCAST)
|
||||
{
|
||||
U2FMessage::error(uMsg->cid, ERR_OTHER);
|
||||
else if (uMsg.cid != CID_BROADCAST) {
|
||||
U2FMessage::error(uMsg.cid, ERR_OTHER);
|
||||
throw runtime_error{ "Invalid CID for init command" };
|
||||
}
|
||||
else if (uMsg->data.size() != INIT_NONCE_SIZE)
|
||||
{
|
||||
U2FMessage::error(uMsg->cid, ERR_INVALID_LEN);
|
||||
} else if (uMsg.data.size() != INIT_NONCE_SIZE) {
|
||||
U2FMessage::error(uMsg.cid, ERR_INVALID_LEN);
|
||||
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{};
|
||||
msg.cid = CID_BROADCAST;
|
||||
msg.cmd = U2FHID_INIT;
|
||||
|
||||
msg.data.insert(msg.data.end(), FIELD(this->nonce));
|
||||
msg.data.insert(msg.data.end(), FIELD(channelID));
|
||||
msg.data.push_back(2); //Protocol version
|
||||
msg.data.push_back(1); //Major device version
|
||||
msg.data.push_back(0); //Minor device version
|
||||
msg.data.push_back(1); //Build device version
|
||||
msg.data.push_back(CAPFLAG_WINK); //Wink capability
|
||||
msg.data.push_back(2); // Protocol version
|
||||
msg.data.push_back(1); // Major device version
|
||||
msg.data.push_back(0); // Minor device version
|
||||
msg.data.push_back(1); // Build device version
|
||||
msg.data.push_back(CAPFLAG_WINK); // Wink capability
|
||||
|
||||
msg.write();
|
||||
}
|
||||
|
||||
@@ -17,16 +17,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "U2FMessage.hpp"
|
||||
#include "U2F_CMD.hpp"
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "U2F_CMD.hpp"
|
||||
#include "U2FMessage.hpp"
|
||||
|
||||
struct U2F_Init_CMD : U2F_CMD
|
||||
{
|
||||
struct U2F_Init_CMD : U2F_CMD {
|
||||
uint64_t nonce;
|
||||
|
||||
public:
|
||||
U2F_Init_CMD(const std::shared_ptr<U2FMessage> uMsg);
|
||||
virtual void respond(const uint32_t channelID) const override;
|
||||
public:
|
||||
U2F_Init_CMD(const U2FMessage& uMsg);
|
||||
virtual void respond(const uint32_t channelID, bool) const override;
|
||||
};
|
||||
|
||||
185
U2F_Msg_CMD.cpp
185
U2F_Msg_CMD.cpp
@@ -18,120 +18,105 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "U2F_Msg_CMD.hpp"
|
||||
#include "APDU.hpp"
|
||||
#include "Field.hpp"
|
||||
#include "Streams.hpp"
|
||||
#include "U2FMessage.hpp"
|
||||
#include "U2F_Authenticate_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 <iostream>
|
||||
#include "Streams.hpp"
|
||||
#include "Field.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
uint32_t U2F_Msg_CMD::getLe(const uint32_t byteCount, vector<uint8_t> bytes)
|
||||
{
|
||||
if (byteCount != 0)
|
||||
{
|
||||
//Le must be length of data in bytes
|
||||
|
||||
switch (byteCount)
|
||||
{
|
||||
uint32_t U2F_Msg_CMD::getLe(const uint32_t byteCount, vector<uint8_t> bytes) {
|
||||
if (byteCount != 0) {
|
||||
// Le must be length of data in bytes
|
||||
|
||||
switch (byteCount) {
|
||||
case 1:
|
||||
return (bytes[0] == 0 ? 256 : bytes[0]);
|
||||
case 2:
|
||||
//Don't handle non-compliance with spec here
|
||||
//This case is only possible if extended Lc used
|
||||
//CBA
|
||||
// Don't handle non-compliance with spec here
|
||||
// This case is only possible if extended Lc used
|
||||
// CBA
|
||||
return (bytes[0] == 0 && bytes[1] == 0 ? 65536 : (bytes[0] << 8) + bytes[1]);
|
||||
case 3:
|
||||
//Don't handle non-compliance with spec here
|
||||
//This case is only possible if extended Lc not used
|
||||
//CBA
|
||||
// Don't handle non-compliance with spec here
|
||||
// This case is only possible if extended Lc not used
|
||||
// CBA
|
||||
if (bytes[0] != 0)
|
||||
throw runtime_error{ "First byte of 3-byte Le should be 0"};
|
||||
throw runtime_error{ "First byte of 3-byte Le should be 0" };
|
||||
return (bytes[1] == 0 && bytes[2] == 0 ? 65536 : (bytes[1] << 8) + bytes[2]);
|
||||
default:
|
||||
throw runtime_error{ "Too much data for command" };
|
||||
}
|
||||
}
|
||||
else
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
shared_ptr<U2F_Msg_CMD> U2F_Msg_CMD::generate(const shared_ptr<U2FMessage> uMsg)
|
||||
{
|
||||
if (uMsg->cmd != U2FHID_MSG)
|
||||
shared_ptr<U2F_Msg_CMD> U2F_Msg_CMD::generate(const U2FMessage& uMsg) {
|
||||
if (uMsg.cmd != U2FHID_MSG)
|
||||
throw runtime_error{ "Failed to get U2F Msg uMsg" };
|
||||
else if (uMsg->data.size() < 4)
|
||||
{
|
||||
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_WRONG_LENGTH);
|
||||
else if (uMsg.data.size() < 4) {
|
||||
U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_WRONG_LENGTH);
|
||||
throw runtime_error{ "Msg data is incorrect size" };
|
||||
}
|
||||
|
||||
U2F_Msg_CMD cmd;
|
||||
auto &dat = uMsg->data;
|
||||
auto& dat = uMsg.data;
|
||||
|
||||
cmd.cla = dat[0];
|
||||
|
||||
if (cmd.cla != 0)
|
||||
{
|
||||
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_COMMAND_NOT_ALLOWED);
|
||||
if (cmd.cla != 0) {
|
||||
U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_COMMAND_NOT_ALLOWED);
|
||||
throw runtime_error{ "Invalid CLA value in U2F Message" };
|
||||
}
|
||||
|
||||
cmd.ins = dat[1];
|
||||
cmd.p1 = dat[2];
|
||||
cmd.p2 = dat[3];
|
||||
cmd.p1 = dat[2];
|
||||
cmd.p2 = dat[3];
|
||||
|
||||
vector<uint8_t> data{ dat.begin() + 4, dat.end() };
|
||||
const uint32_t cBCount = data.size();
|
||||
auto startPtr = data.begin(), endPtr = data.end();
|
||||
|
||||
if (usesData.at(cmd.ins) || data.size() > 3)
|
||||
{
|
||||
if (cBCount == 0)
|
||||
{
|
||||
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_WRONG_LENGTH);
|
||||
const auto cmdUsesData = usesData.find(cmd.ins);
|
||||
|
||||
if (cmdUsesData == usesData.end()) {
|
||||
U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_INS_NOT_SUPPORTED);
|
||||
throw runtime_error{ "Unknown instruction: unsure if uses data" };
|
||||
} else if (cmdUsesData->second || data.size() > 3) {
|
||||
if (cBCount == 0) {
|
||||
U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_WRONG_LENGTH);
|
||||
throw runtime_error{ "Invalid command - should have attached data" };
|
||||
}
|
||||
|
||||
if (data[0] != 0) //1 byte length
|
||||
if (data[0] != 0) // 1 byte length
|
||||
{
|
||||
cmd.lc = data[0];
|
||||
startPtr++;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
cmd.lc = (data[1] << 8) + data[2];
|
||||
startPtr += 3;
|
||||
}
|
||||
|
||||
endPtr = startPtr + cmd.lc;
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
cmd.le = getLe(data.end() - endPtr, vector<uint8_t>(endPtr, data.end()));
|
||||
}
|
||||
catch (runtime_error& ignored)
|
||||
{
|
||||
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_WRONG_LENGTH);
|
||||
} catch (runtime_error& ignored) {
|
||||
U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_WRONG_LENGTH);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
cmd.lc = 0;
|
||||
endPtr = startPtr;
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
cmd.le = getLe(cBCount, data);
|
||||
}
|
||||
catch (runtime_error& ignored)
|
||||
{
|
||||
U2F_Msg_CMD::error(uMsg->cid, APDU_STATUS::SW_WRONG_LENGTH);
|
||||
} catch (runtime_error& ignored) {
|
||||
U2F_Msg_CMD::error(uMsg.cid, APDU_STATUS::SW_WRONG_LENGTH);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -141,42 +126,44 @@ shared_ptr<U2F_Msg_CMD> U2F_Msg_CMD::generate(const shared_ptr<U2FMessage> uMsg)
|
||||
#ifdef DEBUG_STREAMS
|
||||
auto hAS = getHostAPDUStream().get();
|
||||
|
||||
fprintf(hAS, "<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>CLA</th>\n"
|
||||
"\t\t\t\t\t<th>INS</th>\n"
|
||||
"\t\t\t\t\t<th>P1</th>\n"
|
||||
"\t\t\t\t\t<th>P2</th>\n"
|
||||
"\t\t\t\t\t<th>Lc</th>\n"
|
||||
"\t\t\t\t\t<th>Data</th>\n"
|
||||
"\t\t\t\t\t<th>Le</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td>0x%02X</td>\n"
|
||||
"\t\t\t\t\t<td>0x%02X</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%3u</td>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">", cmd.cla, cmd.ins, cmd.p1, cmd.p2, cmd.lc);
|
||||
|
||||
fprintf(hAS,
|
||||
"<table>\n"
|
||||
"\t\t\t<thead>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<th>CLA</th>\n"
|
||||
"\t\t\t\t\t<th>INS</th>\n"
|
||||
"\t\t\t\t\t<th>P1</th>\n"
|
||||
"\t\t\t\t\t<th>P2</th>\n"
|
||||
"\t\t\t\t\t<th>Lc</th>\n"
|
||||
"\t\t\t\t\t<th>Data</th>\n"
|
||||
"\t\t\t\t\t<th>Le</th>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</thead>\n"
|
||||
"\t\t\t<tbody>\n"
|
||||
"\t\t\t\t<tr>\n"
|
||||
"\t\t\t\t\t<td>0x%02X</td>\n"
|
||||
"\t\t\t\t\t<td>0x%02X</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%u</td>\n"
|
||||
"\t\t\t\t\t<td>%3u</td>\n"
|
||||
"\t\t\t\t\t<td class=\"data\">",
|
||||
cmd.cla, cmd.ins, cmd.p1, cmd.p2, cmd.lc);
|
||||
|
||||
for (auto b : dBytes)
|
||||
fprintf(hAS, "%3u ", b);
|
||||
|
||||
fprintf(hAS, "</td>\n"
|
||||
"\t\t\t\t\t<td>%5u</td>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>\n"
|
||||
"\t\t<br />", cmd.le);
|
||||
fprintf(hAS,
|
||||
"</td>\n"
|
||||
"\t\t\t\t\t<td>%5u</td>\n"
|
||||
"\t\t\t\t</tr>\n"
|
||||
"\t\t\t</tbody>\n"
|
||||
"\t\t</table>\n"
|
||||
"\t\t<br />",
|
||||
cmd.le);
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
switch (cmd.ins)
|
||||
{
|
||||
try {
|
||||
switch (cmd.ins) {
|
||||
case APDU::U2F_REG:
|
||||
return make_shared<U2F_Register_APDU>(cmd, dBytes);
|
||||
case APDU::U2F_AUTH:
|
||||
@@ -187,17 +174,14 @@ shared_ptr<U2F_Msg_CMD> U2F_Msg_CMD::generate(const shared_ptr<U2FMessage> uMsg)
|
||||
cerr << "Invalid command used" << endl;
|
||||
throw APDU_STATUS::SW_INS_NOT_SUPPORTED;
|
||||
}
|
||||
}
|
||||
catch (const APDU_STATUS e)
|
||||
{
|
||||
U2F_Msg_CMD::error(uMsg->cid, e);
|
||||
} catch (const APDU_STATUS e) {
|
||||
U2F_Msg_CMD::error(uMsg.cid, e);
|
||||
throw runtime_error{ "APDU construction error" };
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void U2F_Msg_CMD::error(const uint32_t channelID, const uint16_t errCode)
|
||||
{
|
||||
void U2F_Msg_CMD::error(const uint32_t channelID, const uint16_t errCode) {
|
||||
clog << "U2F_Msg_CMD::error " << errCode << endl;
|
||||
U2FMessage msg{};
|
||||
msg.cid = channelID;
|
||||
@@ -206,13 +190,10 @@ void U2F_Msg_CMD::error(const uint32_t channelID, const uint16_t errCode)
|
||||
msg.write();
|
||||
}
|
||||
|
||||
const map<uint8_t, bool> U2F_Msg_CMD::usesData = {
|
||||
{ U2F_REG, true },
|
||||
{ U2F_AUTH, true },
|
||||
{ U2F_VER, false }
|
||||
};
|
||||
const map<uint8_t, bool> U2F_Msg_CMD::usesData = { { U2F_REG, true },
|
||||
{ U2F_AUTH, true },
|
||||
{ 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));
|
||||
}
|
||||
|
||||
@@ -20,27 +20,26 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#include "U2F_CMD.hpp"
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
struct U2F_Msg_CMD : U2F_CMD
|
||||
{
|
||||
uint8_t cla;
|
||||
uint8_t ins;
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
struct U2F_Msg_CMD : public U2F_CMD {
|
||||
uint8_t cla;
|
||||
uint8_t ins;
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
uint32_t lc;
|
||||
uint32_t le;
|
||||
|
||||
const static std::map<uint8_t, bool> usesData;
|
||||
|
||||
protected:
|
||||
static uint32_t getLe(const uint32_t byteCount, std::vector<uint8_t> bytes);
|
||||
U2F_Msg_CMD() = default;
|
||||
protected:
|
||||
static uint32_t getLe(const uint32_t byteCount, std::vector<uint8_t> bytes);
|
||||
U2F_Msg_CMD() = default;
|
||||
virtual ~U2F_Msg_CMD() = default;
|
||||
|
||||
public:
|
||||
static std::shared_ptr<U2F_Msg_CMD> generate(const std::shared_ptr<U2FMessage> uMsg);
|
||||
static void error(const uint32_t channelID, const uint16_t errCode);
|
||||
void respond(const uint32_t channelID) const;
|
||||
public:
|
||||
static std::shared_ptr<U2F_Msg_CMD> generate(const U2FMessage& uMsg);
|
||||
static void error(const uint32_t channelID, const uint16_t errCode);
|
||||
void respond(const uint32_t channelID, bool hasAuthorisation) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,15 +21,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using namespace std;
|
||||
|
||||
U2F_Ping_CMD::U2F_Ping_CMD(const shared_ptr<U2FMessage> uMsg)
|
||||
: nonce{ uMsg->data }
|
||||
{
|
||||
if (uMsg->cmd != U2FHID_PING)
|
||||
U2F_Ping_CMD::U2F_Ping_CMD(const U2FMessage& uMsg) : nonce{ uMsg.data } {
|
||||
if (uMsg.cmd != U2FHID_PING)
|
||||
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{};
|
||||
msg.cid = channelID;
|
||||
msg.cmd = U2FHID_PING;
|
||||
|
||||
@@ -17,16 +17,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "U2FMessage.hpp"
|
||||
#include "U2F_CMD.hpp"
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "U2F_CMD.hpp"
|
||||
#include "U2FMessage.hpp"
|
||||
|
||||
struct U2F_Ping_CMD : U2F_CMD
|
||||
{
|
||||
struct U2F_Ping_CMD : U2F_CMD {
|
||||
std::vector<uint8_t> nonce;
|
||||
|
||||
public:
|
||||
U2F_Ping_CMD(const std::shared_ptr<U2FMessage> uMsg);
|
||||
virtual void respond(const uint32_t channelID) const override;
|
||||
public:
|
||||
U2F_Ping_CMD(const U2FMessage& uMsg);
|
||||
virtual void respond(const uint32_t channelID, bool) const override;
|
||||
};
|
||||
|
||||
@@ -17,73 +17,75 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "U2F_Register_APDU.hpp"
|
||||
#include <exception>
|
||||
#include <cstring>
|
||||
#include "micro-ecc/uECC.h"
|
||||
#include "Certificates.hpp"
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <iostream>
|
||||
#include "APDU.hpp"
|
||||
#include "U2FMessage.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include "Certificates.hpp"
|
||||
#include "Signature.hpp"
|
||||
#include "U2FMessage.hpp"
|
||||
#include "micro-ecc/uECC.h"
|
||||
#include "u2f.hpp"
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <mbedtls/sha256.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
U2F_Register_APDU::U2F_Register_APDU(const U2F_Msg_CMD &msg, const vector<uint8_t> &data)
|
||||
: U2F_Msg_CMD{ msg }
|
||||
{
|
||||
if (data.size() != 64)
|
||||
{
|
||||
//Incorrect registration size
|
||||
U2F_Register_APDU::U2F_Register_APDU(const U2F_Msg_CMD& msg, const vector<uint8_t>& data)
|
||||
: U2F_Msg_CMD{ msg } {
|
||||
if (data.size() != 64) {
|
||||
// Incorrect registration size
|
||||
throw APDU_STATUS::SW_WRONG_LENGTH;
|
||||
}
|
||||
else if ((p1 != 0x00 && p1 != 0x03) || p2 != 0x00) //According to spec, 0x03 not allowed here
|
||||
//However, browsers seem to do it, so...
|
||||
} else if ((p1 != 0x00 && p1 != 0x03) || p2 != 0x00) // According to spec, 0x03 not allowed here
|
||||
// However, browsers seem to do it, so...
|
||||
{
|
||||
//Invalid U2F Message (APDU) parameters detected
|
||||
// Invalid U2F Message (APDU) parameters detected
|
||||
throw APDU_STATUS::SW_INS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
copy(data.data() + 0, data.data() + 32, challengeP.begin());
|
||||
copy(data.data() + 0, data.data() + 32, challengeP.begin());
|
||||
copy(data.data() + 32, data.data() + 64, appP.begin());
|
||||
|
||||
//Use crypto lib to generate keypair
|
||||
// Use crypto lib to generate keypair
|
||||
Storage::PrivKey privKey{};
|
||||
Storage::PubKey pubKey{};
|
||||
|
||||
//First byte must be 0x04 for some reason
|
||||
|
||||
// First byte must be 0x04 for some reason
|
||||
pubKey[0] = 0x04;
|
||||
|
||||
uECC_make_key(pubKey.data() + 1, privKey.data(), uECC_secp256r1());
|
||||
|
||||
this->keyH = Storage::appParams.size(); //To increment the key handle
|
||||
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::privKeys[this->keyH] = privKey;
|
||||
Storage::pubKeys[this->keyH] = pubKey;
|
||||
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{};
|
||||
m.cid = channelID;
|
||||
m.cmd = U2FHID_MSG;
|
||||
|
||||
auto& response = m.data;
|
||||
const auto appParam = Storage::appParams[this->keyH];
|
||||
const auto pubKey = Storage::pubKeys[this->keyH];
|
||||
const auto pubKey = Storage::pubKeys[this->keyH];
|
||||
|
||||
response.push_back(0x05);
|
||||
copy(pubKey.begin(), pubKey.end(), back_inserter(response));
|
||||
response.push_back(sizeof(this->keyH));
|
||||
|
||||
auto fakeKeyHBytes = reinterpret_cast<const uint8_t *>(&this->keyH);
|
||||
auto fakeKeyHBytes = reinterpret_cast<const uint8_t*>(&this->keyH);
|
||||
copy(fakeKeyHBytes, fakeKeyHBytes + sizeof(this->keyH), back_inserter(response));
|
||||
|
||||
copy(attestCert, end(attestCert), back_inserter(response));
|
||||
|
||||
//Gen signature
|
||||
// Gen signature
|
||||
Digest digest;
|
||||
{
|
||||
mbedtls_sha256_context shaContext;
|
||||
@@ -94,13 +96,18 @@ void U2F_Register_APDU::respond(const uint32_t channelID) const
|
||||
uint8_t byteReserved = 0;
|
||||
mbedtls_sha256_update(&shaContext, reinterpret_cast<unsigned char*>(&byteReserved), 1);
|
||||
|
||||
mbedtls_sha256_update(&shaContext, reinterpret_cast<const unsigned char*>(appParam.data()), appParam.size());
|
||||
mbedtls_sha256_update(&shaContext, reinterpret_cast<const unsigned char*>(appParam.data()),
|
||||
appParam.size());
|
||||
|
||||
mbedtls_sha256_update(&shaContext, reinterpret_cast<const unsigned char*>(challengeP.data()), challengeP.size());
|
||||
mbedtls_sha256_update(&shaContext,
|
||||
reinterpret_cast<const unsigned char*>(challengeP.data()),
|
||||
challengeP.size());
|
||||
|
||||
mbedtls_sha256_update(&shaContext, reinterpret_cast<const unsigned char*>(&keyH), sizeof(keyH));
|
||||
mbedtls_sha256_update(&shaContext, reinterpret_cast<const unsigned char*>(&keyH),
|
||||
sizeof(keyH));
|
||||
|
||||
mbedtls_sha256_update(&shaContext, reinterpret_cast<const unsigned char*>(pubKey.data()), pubKey.size());
|
||||
mbedtls_sha256_update(&shaContext, reinterpret_cast<const unsigned char*>(pubKey.data()),
|
||||
pubKey.size());
|
||||
|
||||
mbedtls_sha256_finish(&shaContext, digest.data());
|
||||
mbedtls_sha256_free(&shaContext);
|
||||
@@ -109,7 +116,7 @@ void U2F_Register_APDU::respond(const uint32_t channelID) const
|
||||
Signature signature;
|
||||
uECC_sign(attestPrivKey, digest.data(), digest.size(), signature.data(), uECC_secp256r1());
|
||||
|
||||
//Append signature as DER
|
||||
// Append signature as DER
|
||||
appendSignatureAsDER(response, signature);
|
||||
|
||||
response.push_back(static_cast<uint16_t>(APDU_STATUS::SW_NO_ERROR) >> 8);
|
||||
@@ -117,3 +124,7 @@ void U2F_Register_APDU::respond(const uint32_t channelID) const
|
||||
|
||||
m.write();
|
||||
}
|
||||
|
||||
bool U2F_Register_APDU::requiresAuthorisation() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,18 +17,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "U2F_Msg_CMD.hpp"
|
||||
#include "Storage.hpp"
|
||||
#include "U2F_Msg_CMD.hpp"
|
||||
|
||||
struct U2F_Register_APDU : U2F_Msg_CMD
|
||||
{
|
||||
struct U2F_Register_APDU : U2F_Msg_CMD {
|
||||
std::array<uint8_t, 32> challengeP;
|
||||
Storage::AppParam appP;
|
||||
Storage::KeyHandle keyH;
|
||||
Storage::AppParam appP;
|
||||
Storage::KeyHandle keyH;
|
||||
|
||||
public:
|
||||
U2F_Register_APDU(const U2F_Msg_CMD &msg, const std::vector<uint8_t> &data);
|
||||
public:
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,25 +17,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "U2F_Version_APDU.hpp"
|
||||
#include <iostream>
|
||||
#include "APDU.hpp"
|
||||
#include "Field.hpp"
|
||||
#include "U2FMessage.hpp"
|
||||
#include "u2f.hpp"
|
||||
#include "Field.hpp"
|
||||
#include "APDU.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
U2F_Version_APDU::U2F_Version_APDU(const U2F_Msg_CMD &msg, const std::vector<uint8_t> &data)
|
||||
{
|
||||
//Don't actually respond yet unless invalid
|
||||
U2F_Version_APDU::U2F_Version_APDU(const U2F_Msg_CMD& msg, const std::vector<uint8_t>& data) {
|
||||
// Don't actually respond yet unless invalid
|
||||
if (msg.p1 != 0 || msg.p2 != 0)
|
||||
throw APDU_STATUS::SW_INS_NOT_SUPPORTED;
|
||||
else if (data.size() != 0)
|
||||
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' };
|
||||
U2FMessage m{};
|
||||
|
||||
|
||||
@@ -19,9 +19,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#pragma once
|
||||
#include "U2F_Msg_CMD.hpp"
|
||||
|
||||
struct U2F_Version_APDU : U2F_Msg_CMD
|
||||
{
|
||||
public:
|
||||
U2F_Version_APDU(const U2F_Msg_CMD &msg, const std::vector<uint8_t> &data);
|
||||
void respond(const uint32_t channelID) const override;
|
||||
struct U2F_Version_APDU : U2F_Msg_CMD {
|
||||
public:
|
||||
U2F_Version_APDU(const U2F_Msg_CMD& msg, const std::vector<uint8_t>& data);
|
||||
void respond(const uint32_t channelID, bool) const override;
|
||||
};
|
||||
|
||||
40
_Architecture.hpp
Normal file
40
_Architecture.hpp
Normal 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
|
||||
33
main.cpp
33
main.cpp
@@ -1,38 +1,33 @@
|
||||
#include "U2FDevice.hpp"
|
||||
#include "Architecture.hpp"
|
||||
#include "U2FDevice.hpp"
|
||||
#include <execinfo.h>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef DEBUG_MSGS
|
||||
//Courtesy StackOverflow answer https://stackoverflow.com/a/3356421
|
||||
void terminateHandler()
|
||||
{
|
||||
void *trace_elems[20];
|
||||
int trace_elem_count(backtrace(trace_elems, 20));
|
||||
char **stack_syms(backtrace_symbols(trace_elems, trace_elem_count));
|
||||
for (int i = 0; i < trace_elem_count; ++i)
|
||||
{
|
||||
std::cout << stack_syms[i] << "\n";
|
||||
}
|
||||
free(stack_syms);
|
||||
// Courtesy StackOverflow answer https://stackoverflow.com/a/3356421
|
||||
void terminateHandler() {
|
||||
void* trace_elems[20];
|
||||
int trace_elem_count(backtrace(trace_elems, 20));
|
||||
char** stack_syms(backtrace_symbols(trace_elems, trace_elem_count));
|
||||
for (int i = 0; i < trace_elem_count; ++i) {
|
||||
std::cout << stack_syms[i] << "\n";
|
||||
}
|
||||
free(stack_syms);
|
||||
|
||||
exit(1);
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int main(int argc, char** argv) {
|
||||
#ifdef DEBUG_MSGS
|
||||
std::set_terminate(terminate_handler);
|
||||
#endif
|
||||
int retCode = handleTransactions(argv[0], argc == 2 ? argv[1] : STORAGE_PREFIX);
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
initialiseLights(argv[0]);
|
||||
deinitialiseLights(argv[0]);
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << "Exception in code: " << e.what() << std::endl;
|
||||
throw;
|
||||
}
|
||||
|
||||
BIN
precompiled/kernel-hid-u2f.img
Normal file
BIN
precompiled/kernel-hid-u2f.img
Normal file
Binary file not shown.
141
u2f.hpp
141
u2f.hpp
@@ -5,123 +5,122 @@
|
||||
#ifndef __U2FHID_H_INCLUDED__
|
||||
#define __U2FHID_H_INCLUDED__
|
||||
|
||||
#ifdef _MSC_VER // Windows
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
#ifdef _MSC_VER // Windows
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned long int uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
# include <stdint.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Size of HID reports
|
||||
// Size of HID reports
|
||||
|
||||
#define HID_RPT_SIZE 64 // Default size of raw HID report
|
||||
|
||||
#define HID_RPT_SIZE 64 // Default size of raw HID report
|
||||
|
||||
// Frame layout - command- and continuation frames
|
||||
|
||||
#define CID_BROADCAST 0xffffffff // Broadcast channel id
|
||||
#define CID_BROADCAST 0xffffffff // Broadcast channel id
|
||||
|
||||
#define TYPE_MASK 0x80 // Frame type mask
|
||||
#define TYPE_INIT 0x80 // Initial frame identifier
|
||||
#define TYPE_CONT 0x00 // Continuation frame identifier
|
||||
#define TYPE_MASK 0x80 // Frame type mask
|
||||
#define TYPE_INIT 0x80 // Initial frame identifier
|
||||
#define TYPE_CONT 0x00 // Continuation frame identifier
|
||||
|
||||
typedef struct {
|
||||
uint32_t cid; // Channel identifier
|
||||
union {
|
||||
uint8_t type; // Frame type - b7 defines type
|
||||
struct {
|
||||
uint8_t cmd; // Command - b7 set
|
||||
uint8_t bcnth; // Message byte count - high part
|
||||
uint8_t bcntl; // Message byte count - low part
|
||||
uint8_t data[HID_RPT_SIZE - 7]; // Data payload
|
||||
} init;
|
||||
struct {
|
||||
uint8_t seq; // Sequence number - b7 cleared
|
||||
uint8_t data[HID_RPT_SIZE - 5]; // Data payload
|
||||
} cont;
|
||||
};
|
||||
uint32_t cid; // Channel identifier
|
||||
union {
|
||||
uint8_t type; // Frame type - b7 defines type
|
||||
struct {
|
||||
uint8_t cmd; // Command - b7 set
|
||||
uint8_t bcnth; // Message byte count - high part
|
||||
uint8_t bcntl; // Message byte count - low part
|
||||
uint8_t data[HID_RPT_SIZE - 7]; // Data payload
|
||||
} init;
|
||||
struct {
|
||||
uint8_t seq; // Sequence number - b7 cleared
|
||||
uint8_t data[HID_RPT_SIZE - 5]; // Data payload
|
||||
} cont;
|
||||
};
|
||||
} U2FHID_FRAME;
|
||||
|
||||
#define FRAME_TYPE(f) ((f).type & TYPE_MASK)
|
||||
#define FRAME_CMD(f) ((f).init.cmd & ~TYPE_MASK)
|
||||
#define MSG_LEN(f) ((f).init.bcnth*256 + (f).init.bcntl)
|
||||
#define FRAME_SEQ(f) ((f).cont.seq & ~TYPE_MASK)
|
||||
#define FRAME_CMD(f) ((f).init.cmd & ~TYPE_MASK)
|
||||
#define MSG_LEN(f) ((f).init.bcnth * 256 + (f).init.bcntl)
|
||||
#define FRAME_SEQ(f) ((f).cont.seq & ~TYPE_MASK)
|
||||
|
||||
// HID usage- and usage-page definitions
|
||||
|
||||
#define FIDO_USAGE_PAGE 0xf1d0 // FIDO alliance HID usage page
|
||||
#define FIDO_USAGE_U2FHID 0x01 // U2FHID usage for top-level collection
|
||||
#define FIDO_USAGE_DATA_IN 0x20 // Raw IN data report
|
||||
#define FIDO_USAGE_DATA_OUT 0x21 // Raw OUT data report
|
||||
|
||||
// General constants
|
||||
#define FIDO_USAGE_PAGE 0xf1d0 // FIDO alliance HID usage page
|
||||
#define FIDO_USAGE_U2FHID 0x01 // U2FHID usage for top-level collection
|
||||
#define FIDO_USAGE_DATA_IN 0x20 // Raw IN data report
|
||||
#define FIDO_USAGE_DATA_OUT 0x21 // Raw OUT data report
|
||||
|
||||
#define U2FHID_IF_VERSION 2 // Current interface implementation version
|
||||
#define U2FHID_TRANS_TIMEOUT 3000 // Default message timeout in ms
|
||||
// General constants
|
||||
|
||||
#define U2FHID_IF_VERSION 2 // Current interface implementation version
|
||||
#define U2FHID_TRANS_TIMEOUT 3000 // Default message timeout in ms
|
||||
|
||||
// U2FHID native commands
|
||||
|
||||
#define U2FHID_PING (TYPE_INIT | 0x01) // Echo data through local processor only
|
||||
#define U2FHID_MSG (TYPE_INIT | 0x03) // Send U2F message frame
|
||||
#define U2FHID_LOCK (TYPE_INIT | 0x04) // Send lock channel command
|
||||
#define U2FHID_INIT (TYPE_INIT | 0x06) // Channel initialization
|
||||
#define U2FHID_WINK (TYPE_INIT | 0x08) // Send device identification wink
|
||||
#define U2FHID_SYNC (TYPE_INIT | 0x3c) // Protocol resync command
|
||||
#define U2FHID_ERROR (TYPE_INIT | 0x3f) // Error response
|
||||
#define U2FHID_PING (TYPE_INIT | 0x01) // Echo data through local processor only
|
||||
#define U2FHID_MSG (TYPE_INIT | 0x03) // Send U2F message frame
|
||||
#define U2FHID_LOCK (TYPE_INIT | 0x04) // Send lock channel command
|
||||
#define U2FHID_INIT (TYPE_INIT | 0x06) // Channel initialization
|
||||
#define U2FHID_WINK (TYPE_INIT | 0x08) // Send device identification wink
|
||||
#define U2FHID_SYNC (TYPE_INIT | 0x3c) // Protocol resync command
|
||||
#define U2FHID_ERROR (TYPE_INIT | 0x3f) // Error response
|
||||
|
||||
#define U2FHID_VENDOR_FIRST (TYPE_INIT | 0x40) // First vendor defined command
|
||||
#define U2FHID_VENDOR_LAST (TYPE_INIT | 0x7f) // Last vendor defined command
|
||||
|
||||
#define U2FHID_VENDOR_FIRST (TYPE_INIT | 0x40) // First vendor defined command
|
||||
#define U2FHID_VENDOR_LAST (TYPE_INIT | 0x7f) // Last vendor defined command
|
||||
|
||||
// U2FHID_INIT command defines
|
||||
|
||||
#define INIT_NONCE_SIZE 8 // Size of channel initialization challenge
|
||||
#define CAPFLAG_WINK 0x01 // Device supports WINK command
|
||||
#define INIT_NONCE_SIZE 8 // Size of channel initialization challenge
|
||||
#define CAPFLAG_WINK 0x01 // Device supports WINK command
|
||||
|
||||
typedef struct {
|
||||
uint8_t nonce[INIT_NONCE_SIZE]; // Client application nonce
|
||||
uint8_t nonce[INIT_NONCE_SIZE]; // Client application nonce
|
||||
} U2FHID_INIT_REQ;
|
||||
|
||||
typedef struct {
|
||||
uint8_t nonce[INIT_NONCE_SIZE]; // Client application nonce
|
||||
uint32_t cid; // Channel identifier
|
||||
uint8_t versionInterface; // Interface version
|
||||
uint8_t versionMajor; // Major version number
|
||||
uint8_t versionMinor; // Minor version number
|
||||
uint8_t versionBuild; // Build version number
|
||||
uint8_t capFlags; // Capabilities flags
|
||||
uint8_t nonce[INIT_NONCE_SIZE]; // Client application nonce
|
||||
uint32_t cid; // Channel identifier
|
||||
uint8_t versionInterface; // Interface version
|
||||
uint8_t versionMajor; // Major version number
|
||||
uint8_t versionMinor; // Minor version number
|
||||
uint8_t versionBuild; // Build version number
|
||||
uint8_t capFlags; // Capabilities flags
|
||||
} U2FHID_INIT_RESP;
|
||||
|
||||
// U2FHID_SYNC command defines
|
||||
|
||||
typedef struct {
|
||||
uint8_t nonce; // Client application nonce
|
||||
uint8_t nonce; // Client application nonce
|
||||
} U2FHID_SYNC_REQ;
|
||||
|
||||
typedef struct {
|
||||
uint8_t nonce; // Client application nonce
|
||||
uint8_t nonce; // Client application nonce
|
||||
} U2FHID_SYNC_RESP;
|
||||
|
||||
// Low-level error codes. Return as negatives.
|
||||
|
||||
#define ERR_NONE 0x00 // No error
|
||||
#define ERR_INVALID_CMD 0x01 // Invalid command
|
||||
#define ERR_INVALID_PAR 0x02 // Invalid parameter
|
||||
#define ERR_INVALID_LEN 0x03 // Invalid message length
|
||||
#define ERR_INVALID_SEQ 0x04 // Invalid message sequencing
|
||||
#define ERR_MSG_TIMEOUT 0x05 // Message has timed out
|
||||
#define ERR_CHANNEL_BUSY 0x06 // Channel busy
|
||||
#define ERR_LOCK_REQUIRED 0x0a // Command requires channel lock
|
||||
#define ERR_SYNC_FAIL 0x0b // SYNC command failed
|
||||
#define ERR_OTHER 0x7f // Other unspecified error
|
||||
#define ERR_NONE 0x00 // No error
|
||||
#define ERR_INVALID_CMD 0x01 // Invalid command
|
||||
#define ERR_INVALID_PAR 0x02 // Invalid parameter
|
||||
#define ERR_INVALID_LEN 0x03 // Invalid message length
|
||||
#define ERR_INVALID_SEQ 0x04 // Invalid message sequencing
|
||||
#define ERR_MSG_TIMEOUT 0x05 // Message has timed out
|
||||
#define ERR_CHANNEL_BUSY 0x06 // Channel busy
|
||||
#define ERR_LOCK_REQUIRED 0x0a // Command requires channel lock
|
||||
#define ERR_SYNC_FAIL 0x0b // SYNC command failed
|
||||
#define ERR_OTHER 0x7f // Other unspecified error
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // __U2FHID_H_INCLUDED__
|
||||
|
||||
#endif // __U2FHID_H_INCLUDED__
|
||||
|
||||
Reference in New Issue
Block a user