mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-11 06:09:08 -07:00
u2f over nfc
https://github.com/flipperdevices/flipperzero-firmware/pull/1755 from @devsnek may need more testing
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
#define U2F_CMD_REGISTER 0x01
|
||||
#define U2F_CMD_AUTHENTICATE 0x02
|
||||
#define U2F_CMD_VERSION 0x03
|
||||
#define U2F_CMD_APPLET_SELECTION 0xA4
|
||||
|
||||
typedef enum {
|
||||
U2fCheckOnly = 0x07, // "check-only" - only check key handle, don't send auth response
|
||||
@@ -37,11 +38,6 @@ typedef struct {
|
||||
} __attribute__((packed)) U2fKeyHandle;
|
||||
|
||||
typedef struct {
|
||||
uint8_t cla;
|
||||
uint8_t ins;
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
uint8_t len[3];
|
||||
uint8_t challenge[32];
|
||||
uint8_t app_id[32];
|
||||
} __attribute__((packed)) U2fRegisterReq;
|
||||
@@ -54,11 +50,6 @@ typedef struct {
|
||||
} __attribute__((packed)) U2fRegisterResp;
|
||||
|
||||
typedef struct {
|
||||
uint8_t cla;
|
||||
uint8_t ins;
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
uint8_t len[3];
|
||||
uint8_t challenge[32];
|
||||
uint8_t app_id[32];
|
||||
U2fKeyHandle key_handle;
|
||||
@@ -72,10 +63,14 @@ typedef struct {
|
||||
|
||||
static const uint8_t ver_str[] = {"U2F_V2"};
|
||||
|
||||
// NFC applet selection fields
|
||||
static const uint8_t rid_ac_ax[] = { 0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01 };
|
||||
|
||||
static const uint8_t state_no_error[] = {0x90, 0x00};
|
||||
static const uint8_t state_not_supported[] = {0x6D, 0x00};
|
||||
static const uint8_t state_user_missing[] = {0x69, 0x85};
|
||||
static const uint8_t state_wrong_data[] = {0x6A, 0x80};
|
||||
static const uint8_t state_app_not_found[] = { 0x6A, 0x82 };
|
||||
|
||||
struct U2fData {
|
||||
uint8_t device_key[32];
|
||||
@@ -88,6 +83,17 @@ struct U2fData {
|
||||
void* context;
|
||||
};
|
||||
|
||||
static void* apdu_command_data(U2fApduCommand* cmd) {
|
||||
// Short encoding is a single byte.
|
||||
// Extended length encoding is 0 byte followed by MSB and LSB bytes.
|
||||
if (cmd->len[0] == 0) {
|
||||
return cmd->len + 3;
|
||||
}
|
||||
else {
|
||||
return cmd->len + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int u2f_uecc_random(uint8_t* dest, unsigned size) {
|
||||
furi_hal_random_fill_buf(dest, size);
|
||||
return 1;
|
||||
@@ -178,9 +184,10 @@ static uint8_t u2f_der_encode_signature(uint8_t* der, uint8_t* sig) {
|
||||
return len;
|
||||
}
|
||||
|
||||
static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
||||
U2fRegisterReq* req = (U2fRegisterReq*)buf;
|
||||
U2fRegisterResp* resp = (U2fRegisterResp*)buf;
|
||||
static uint16_t u2f_register(U2fData* U2F, const uint8_t* in_buf, uint8_t* out_buf) {
|
||||
U2fApduCommand* cmd = (U2fApduCommand*)in_buf;
|
||||
U2fRegisterReq* req = apdu_command_data(cmd);
|
||||
U2fRegisterResp* resp = (U2fRegisterResp*)out_buf;
|
||||
U2fKeyHandle handle;
|
||||
uint8_t private[32];
|
||||
U2fPubKey pub_key;
|
||||
@@ -190,13 +197,13 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
||||
if(u2f_data_check(false) == false) {
|
||||
U2F->ready = false;
|
||||
if(U2F->callback != NULL) U2F->callback(U2fNotifyError, U2F->context);
|
||||
memcpy(&buf[0], state_not_supported, 2);
|
||||
memcpy(&out_buf[0], state_not_supported, 2);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if(U2F->callback != NULL) U2F->callback(U2fNotifyRegister, U2F->context);
|
||||
if(U2F->user_present == false) {
|
||||
memcpy(&buf[0], state_user_missing, 2);
|
||||
memcpy(&out_buf[0], state_user_missing, 2);
|
||||
return 2;
|
||||
}
|
||||
U2F->user_present = false;
|
||||
@@ -247,9 +254,10 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
||||
return (sizeof(U2fRegisterResp) + cert_len + signature_len + 2);
|
||||
}
|
||||
|
||||
static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
U2fAuthReq* req = (U2fAuthReq*)buf;
|
||||
U2fAuthResp* resp = (U2fAuthResp*)buf;
|
||||
static uint16_t u2f_authenticate(U2fData* U2F, const uint8_t* in_buf, uint8_t* out_buf) {
|
||||
U2fApduCommand* cmd = (U2fApduCommand*)in_buf;
|
||||
U2fAuthReq* req = apdu_command_data(cmd);
|
||||
U2fAuthResp* resp = (U2fAuthResp*)out_buf;
|
||||
uint8_t priv_key[32];
|
||||
uint8_t mac_control[32];
|
||||
hmac_sha256_context hmac_ctx;
|
||||
@@ -262,7 +270,7 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
if(u2f_data_check(false) == false) {
|
||||
U2F->ready = false;
|
||||
if(U2F->callback != NULL) U2F->callback(U2fNotifyError, U2F->context);
|
||||
memcpy(&buf[0], state_not_supported, 2);
|
||||
memcpy(&out_buf[0], state_not_supported, 2);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -270,8 +278,8 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
if(U2F->user_present == true) {
|
||||
flags |= 1;
|
||||
} else {
|
||||
if(req->p1 == U2fEnforce) {
|
||||
memcpy(&buf[0], state_user_missing, 2);
|
||||
if (cmd->p1 == U2fEnforce) {
|
||||
memcpy(&out_buf[0], state_user_missing, 2);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
@@ -302,12 +310,12 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
|
||||
if(memcmp(req->key_handle.hash, mac_control, 32) != 0) {
|
||||
FURI_LOG_W(TAG, "Wrong handle!");
|
||||
memcpy(&buf[0], state_wrong_data, 2);
|
||||
memcpy(&out_buf[0], state_wrong_data, 2);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if(req->p1 == U2fCheckOnly) { // Check-only: don't need to send full response
|
||||
memcpy(&buf[0], state_user_missing, 2);
|
||||
if (cmd->p1 == U2fCheckOnly) { // Check-only: don't need to send full response
|
||||
memcpy(&out_buf[0], state_user_missing, 2);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -327,36 +335,69 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
return (sizeof(U2fAuthResp) + signature_len + 2);
|
||||
}
|
||||
|
||||
uint16_t u2f_msg_parse(U2fData* U2F, uint8_t* buf, uint16_t len) {
|
||||
uint16_t u2f_applet_selection(const uint8_t* in_buf, uint8_t* out_buf) {
|
||||
U2fApduCommand* cmd = (U2fApduCommand*)in_buf;
|
||||
uint8_t* data = apdu_command_data(cmd);
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"len=%d %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
cmd->len[0],
|
||||
data[0],
|
||||
data[1],
|
||||
data[2],
|
||||
data[3],
|
||||
data[4],
|
||||
data[5],
|
||||
data[6],
|
||||
data[7]);
|
||||
|
||||
if (cmd->len[0] != 8 || memcmp(rid_ac_ax, data, 8) != 0) {
|
||||
memcpy(&out_buf[0], state_app_not_found, 2);
|
||||
return 2;
|
||||
}
|
||||
|
||||
memcpy(&out_buf[0], ver_str, 6);
|
||||
memcpy(&out_buf[6], state_no_error, 2);
|
||||
return 8;
|
||||
}
|
||||
|
||||
uint16_t u2f_msg_parse(U2fData* U2F, const uint8_t* in_buf, uint16_t in_len, uint8_t* out_buf) {
|
||||
furi_assert(U2F);
|
||||
if(!U2F->ready) return 0;
|
||||
if((buf[0] != 0x00) && (len < 5)) return 0;
|
||||
if(buf[1] == U2F_CMD_REGISTER) { // Register request
|
||||
return u2f_register(U2F, buf);
|
||||
|
||||
} else if(buf[1] == U2F_CMD_AUTHENTICATE) { // Authenticate request
|
||||
return u2f_authenticate(U2F, buf);
|
||||
|
||||
} else if(buf[1] == U2F_CMD_VERSION) { // Get U2F version string
|
||||
memcpy(&buf[0], ver_str, 6);
|
||||
memcpy(&buf[6], state_no_error, 2);
|
||||
if (!U2F->ready) return 0;
|
||||
if ((in_buf[0] != 0x00) && (in_len < 5)) return 0;
|
||||
FURI_LOG_D(TAG, "ins=0x%02x", in_buf[1]);
|
||||
if (in_buf[1] == U2F_CMD_REGISTER) { // Register request
|
||||
return u2f_register(U2F, in_buf, out_buf);
|
||||
}
|
||||
else if (in_buf[1] == U2F_CMD_AUTHENTICATE) { // Authenticate request
|
||||
return u2f_authenticate(U2F, in_buf, out_buf);
|
||||
}
|
||||
else if (in_buf[1] == U2F_CMD_VERSION) { // Get U2F version string
|
||||
memcpy(&out_buf[0], ver_str, 6);
|
||||
memcpy(&out_buf[6], state_no_error, 2);
|
||||
return 8;
|
||||
} else {
|
||||
memcpy(&buf[0], state_not_supported, 2);
|
||||
}
|
||||
else if (in_buf[1] == U2F_CMD_APPLET_SELECTION) {
|
||||
return u2f_applet_selection(in_buf, out_buf);
|
||||
}
|
||||
else {
|
||||
memcpy(&out_buf[0], state_not_supported, 2);
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void u2f_wink(U2fData* U2F) {
|
||||
if(U2F->callback != NULL) U2F->callback(U2fNotifyWink, U2F->context);
|
||||
if (U2F->callback != NULL) U2F->callback(U2fNotifyWink, U2F->context);
|
||||
}
|
||||
|
||||
void u2f_set_state(U2fData* U2F, uint8_t state) {
|
||||
if(state == 0) {
|
||||
if(U2F->callback != NULL) U2F->callback(U2fNotifyDisconnect, U2F->context);
|
||||
} else {
|
||||
if(U2F->callback != NULL) U2F->callback(U2fNotifyConnect, U2F->context);
|
||||
if (state == 0) {
|
||||
if (U2F->callback != NULL) U2F->callback(U2fNotifyDisconnect, U2F->context);
|
||||
}
|
||||
else {
|
||||
if (U2F->callback != NULL) U2F->callback(U2fNotifyConnect, U2F->context);
|
||||
}
|
||||
U2F->user_present = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user