Skip to content

Commit

Permalink
upd picopass
Browse files Browse the repository at this point in the history
  • Loading branch information
xMasterX committed Apr 9, 2024
1 parent a7df03f commit bf06a6f
Show file tree
Hide file tree
Showing 12 changed files with 187 additions and 30 deletions.
103 changes: 85 additions & 18 deletions base_pack/picopass/picopass_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <picopass_icons.h>

#include <toolbox/protocols/protocol_dict.h>
#include <toolbox/hex.h>
#include <lfrfid/protocols/lfrfid_protocols.h>
#include <lfrfid/lfrfid_dict_file.h>

Expand All @@ -15,13 +16,21 @@ static const uint32_t picopass_file_version = 1;

const uint8_t picopass_iclass_decryptionkey[] =
{0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36};
const char unknown_block[] = "?? ?? ?? ?? ?? ?? ?? ??";

PicopassDevice* picopass_device_alloc() {
PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice));
picopass_dev->dev_data.auth = PicopassDeviceAuthMethodUnset;
picopass_dev->dev_data.pacs.legacy = false;
picopass_dev->dev_data.pacs.se_enabled = false;
picopass_dev->dev_data.pacs.sio = false;
picopass_dev->dev_data.pacs.biometrics = false;
memset(picopass_dev->dev_data.pacs.key, 0, sizeof(picopass_dev->dev_data.pacs.key));
picopass_dev->dev_data.pacs.elite_kdf = false;
picopass_dev->dev_data.pacs.pin_length = 0;
picopass_dev->dev_data.pacs.bitLength = 0;
memset(
picopass_dev->dev_data.pacs.credential, 0, sizeof(picopass_dev->dev_data.pacs.credential));
picopass_dev->storage = furi_record_open(RECORD_STORAGE);
picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS);
picopass_dev->load_path = furi_string_alloc();
Expand Down Expand Up @@ -139,6 +148,7 @@ static bool picopass_device_save_file_lfrfid(PicopassDevice* dev, FuriString* fi
FURI_LOG_D(TAG, "LFRFID Brief: %s", furi_string_get_cstr(briefStr));
furi_string_free(briefStr);

storage_simply_mkdir(dev->storage, EXT_PATH("lfrfid"));
result = lfrfid_dict_file_save(dict, protocol, furi_string_get_cstr(file_path));
if(result) {
FURI_LOG_D(TAG, "Written: %d", result);
Expand All @@ -157,7 +167,7 @@ static bool picopass_device_save_file(
const char* extension,
bool use_load_path) {
furi_assert(dev);
FURI_LOG_D(TAG, "Save File");
FURI_LOG_D(TAG, "Save File %s %s %s", folder, dev_name, extension);

bool saved = false;
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
Expand All @@ -169,6 +179,7 @@ static bool picopass_device_save_file(
if(dev->format == PicopassDeviceSaveFormatPartial) {
// Clear key that may have been set when doing key tests for legacy
memset(card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0, PICOPASS_BLOCK_LEN);
card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid = false;
}

do {
Expand Down Expand Up @@ -203,13 +214,21 @@ static bool picopass_device_save_file(
PICOPASS_MAX_APP_LIMIT;
for(size_t i = 0; i < app_limit; i++) {
furi_string_printf(temp_str, "Block %d", i);
if(!flipper_format_write_hex(
file,
furi_string_get_cstr(temp_str),
card_data[i].data,
PICOPASS_BLOCK_LEN)) {
block_saved = false;
break;
if(card_data[i].valid) {
if(!flipper_format_write_hex(
file,
furi_string_get_cstr(temp_str),
card_data[i].data,
PICOPASS_BLOCK_LEN)) {
block_saved = false;
break;
}
} else {
if(!flipper_format_write_string_cstr(
file, furi_string_get_cstr(temp_str), unknown_block)) {
block_saved = false;
break;
}
}
}
if(!block_saved) break;
Expand All @@ -234,10 +253,10 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) {
return picopass_device_save_file(
dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true);
} else if(dev->format == PicopassDeviceSaveFormatLF) {
return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true);
return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", false);
} else if(dev->format == PicopassDeviceSaveFormatSeader) {
return picopass_device_save_file(
dev, dev_name, EXT_PATH("apps_data/seader"), ".credential", true);
dev, dev_name, EXT_PATH("apps_data/seader"), ".credential", false);
} else if(dev->format == PicopassDeviceSaveFormatPartial) {
return picopass_device_save_file(
dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true);
Expand All @@ -246,6 +265,19 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) {
return false;
}

bool picopass_hex_str_to_uint8(const char* value_str, uint8_t* value) {
furi_check(value_str);
furi_check(value);

bool parse_success = false;
while(*value_str && value_str[1]) {
parse_success = hex_char_to_uint8(*value_str, value_str[1], value++);
if(!parse_success) break;
value_str += 3;
}
return parse_success;
}

static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, bool show_dialog) {
bool parsed = false;
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
Expand All @@ -260,43 +292,69 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo
}

do {
picopass_device_data_clear(&dev->dev_data);
if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break;

// Read and verify file header
uint32_t version = 0;
if(!flipper_format_read_header(file, temp_str, &version)) break;
if(furi_string_cmp_str(temp_str, picopass_file_header) ||
if(!furi_string_equal_str(temp_str, picopass_file_header) ||
(version != picopass_file_version)) {
deprecated_version = true;
break;
}

FuriString* block_str = furi_string_alloc();
// Parse header blocks
bool block_read = true;
for(size_t i = 0; i < 6; i++) {
furi_string_printf(temp_str, "Block %d", i);
if(!flipper_format_read_hex(
file, furi_string_get_cstr(temp_str), card_data[i].data, PICOPASS_BLOCK_LEN)) {
if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) {
block_read = false;
break;
}
if(furi_string_equal_str(block_str, unknown_block)) {
FURI_LOG_D(TAG, "Block %i: %s (unknown)", i, furi_string_get_cstr(block_str));
card_data[i].valid = false;
memset(card_data[i].data, 0, PICOPASS_BLOCK_LEN);
} else {
FURI_LOG_D(TAG, "Block %i: %s (hex)", i, furi_string_get_cstr(block_str));
if(!picopass_hex_str_to_uint8(furi_string_get_cstr(block_str), card_data[i].data)) {
block_read = false;
break;
}
card_data[i].valid = true;
}
}

size_t app_limit = card_data[PICOPASS_CONFIG_BLOCK_INDEX].data[0];
// Fix for unpersonalized cards that have app_limit set to 0xFF
if(app_limit > PICOPASS_MAX_APP_LIMIT) app_limit = PICOPASS_MAX_APP_LIMIT;
for(size_t i = 6; i < app_limit; i++) {
furi_string_printf(temp_str, "Block %d", i);
if(!flipper_format_read_hex(
file, furi_string_get_cstr(temp_str), card_data[i].data, PICOPASS_BLOCK_LEN)) {
if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) {
block_read = false;
break;
}
if(furi_string_equal_str(block_str, unknown_block)) {
FURI_LOG_D(TAG, "Block %i: %s (unknown)", i, furi_string_get_cstr(block_str));
card_data[i].valid = false;
memset(card_data[i].data, 0, PICOPASS_BLOCK_LEN);
} else {
FURI_LOG_D(TAG, "Block %i: %s (hex)", i, furi_string_get_cstr(block_str));
if(!picopass_hex_str_to_uint8(furi_string_get_cstr(block_str), card_data[i].data)) {
block_read = false;
break;
}
card_data[i].valid = true;
}
}
if(!block_read) break;

picopass_device_parse_credential(card_data, pacs);
picopass_device_parse_wiegand(pacs->credential, pacs);
if(card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].valid) {
picopass_device_parse_credential(card_data, pacs);
picopass_device_parse_wiegand(pacs);
}

parsed = true;
} while(false);
Expand Down Expand Up @@ -371,14 +429,22 @@ void picopass_device_data_clear(PicopassDeviceData* dev_data) {
memset(dev_data->card_data[i].data, 0, sizeof(dev_data->card_data[i].data));
dev_data->card_data[i].valid = false;
}
memset(dev_data->pacs.credential, 0, sizeof(dev_data->pacs.credential));
dev_data->auth = PicopassDeviceAuthMethodUnset;
dev_data->pacs.legacy = false;
dev_data->pacs.se_enabled = false;
dev_data->pacs.elite_kdf = false;
dev_data->pacs.sio = false;
dev_data->pacs.pin_length = 0;
dev_data->pacs.bitLength = 0;
}

bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) {
furi_assert(dev);
if(dev->format != PicopassDeviceSaveFormatHF) {
// Never delete other formats (LF, Seader, etc)
return false;
}

bool deleted = false;
FuriString* file_path;
Expand Down Expand Up @@ -450,7 +516,8 @@ void picopass_device_parse_credential(PicopassBlock* card_data, PicopassPacs* pa
pacs->sio = (card_data[10].data[0] == 0x30); // rough check
}

void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs) {
void picopass_device_parse_wiegand(PicopassPacs* pacs) {
uint8_t* credential = pacs->credential;
uint32_t* halves = (uint32_t*)credential;
if(halves[0] == 0) {
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
Expand Down
11 changes: 10 additions & 1 deletion base_pack/picopass/picopass_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ typedef enum {
PicopassDeviceSaveFormatPartial,
} PicopassDeviceSaveFormat;

typedef enum {
PicopassDeviceAuthMethodUnset,
PicopassDeviceAuthMethodNone, // unsecured picopass
PicopassDeviceAuthMethodKey,
PicopassDeviceAuthMethodNrMac,
PicopassDeviceAuthMethodFailed,
} PicopassDeviceAuthMethod;

typedef enum {
PicopassEmulatorStateHalt,
PicopassEmulatorStateIdle,
Expand Down Expand Up @@ -105,6 +113,7 @@ typedef struct {
typedef struct {
PicopassBlock card_data[PICOPASS_MAX_APP_LIMIT];
PicopassPacs pacs;
PicopassDeviceAuthMethod auth;
} PicopassDeviceData;

typedef struct {
Expand Down Expand Up @@ -150,5 +159,5 @@ void picopass_device_set_loading_callback(
void* context);

void picopass_device_parse_credential(PicopassBlock* card_data, PicopassPacs* pacs);
void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs);
void picopass_device_parse_wiegand(PicopassPacs* pacs);
bool picopass_device_hid_csn(PicopassDevice* dev);
2 changes: 1 addition & 1 deletion base_pack/picopass/protocol/picopass_listener.c
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ PicopassListenerCommand
uint8_t rmac[4] = {};
uint8_t tmac[4] = {};
const uint8_t* key = instance->data->card_data[instance->key_block_num].data;
bool no_key = picopass_is_memset(key, 0x00, PICOPASS_BLOCK_LEN);
bool no_key = !instance->data->card_data[instance->key_block_num].valid;
const uint8_t* rx_data = bit_buffer_get_data(buf);

if(no_key) {
Expand Down
7 changes: 6 additions & 1 deletion base_pack/picopass/protocol/picopass_poller.c
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ NfcCommand picopass_poller_check_security(PicopassPoller* instance) {
case PICOPASS_FUSE_CRYPT0:
FURI_LOG_D(TAG, "Non-secured page, skipping auth");
instance->secured = false;
instance->data->auth = PicopassDeviceAuthMethodNone;
picopass_poller_prepare_read(instance);
instance->state = PicopassPollerStateReadBlock;
return command;
Expand Down Expand Up @@ -193,6 +194,8 @@ NfcCommand picopass_poller_check_security(PicopassPoller* instance) {
FURI_LOG_D(TAG, "SE enabled");
}

// Assume failure since we must auth, correct value will be set on success
instance->data->auth = PicopassDeviceAuthMethodFailed;
if(instance->mode == PicopassPollerModeRead) {
// Always try the NR-MAC auth in case we have the file.
instance->state = PicopassPollerStateNrMacAuth;
Expand Down Expand Up @@ -295,6 +298,7 @@ NfcCommand picopass_poller_nr_mac_auth(PicopassPoller* instance) {
PicopassCheckResp check_resp = {};
error = picopass_poller_check(instance, nr_mac, &mac, &check_resp);
if(error == PicopassErrorNone) {
instance->data->auth = PicopassDeviceAuthMethodNrMac;
memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
if(instance->mode == PicopassPollerModeRead) {
picopass_poller_prepare_read(instance);
Expand Down Expand Up @@ -383,6 +387,7 @@ NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) {
error = picopass_poller_check(instance, NULL, &mac, &check_resp);
if(error == PicopassErrorNone) {
FURI_LOG_I(TAG, "Found key");
instance->data->auth = PicopassDeviceAuthMethodKey;
memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
if(instance->mode == PicopassPollerModeRead) {
memcpy(
Expand Down Expand Up @@ -463,7 +468,7 @@ NfcCommand picopass_poller_parse_credential_handler(PicopassPoller* instance) {
NfcCommand picopass_poller_parse_wiegand_handler(PicopassPoller* instance) {
NfcCommand command = NfcCommandContinue;

picopass_device_parse_wiegand(instance->data->pacs.credential, &instance->data->pacs);
picopass_device_parse_wiegand(&instance->data->pacs);
instance->state = PicopassPollerStateSuccess;
return command;
}
Expand Down
3 changes: 1 addition & 2 deletions base_pack/picopass/scenes/picopass_scene_card_menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ void picopass_scene_card_menu_on_enter(void* context) {
bool zero_config = picopass_is_memset(
card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0x00, PICOPASS_BLOCK_LEN);
bool no_credential = picopass_is_memset(pacs->credential, 0x00, sizeof(pacs->credential));
bool no_key = picopass_is_memset(
card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN);
bool no_key = !card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid;

if(secured && zero_config) {
submenu_add_item(
Expand Down
3 changes: 1 addition & 2 deletions base_pack/picopass/scenes/picopass_scene_device_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ bool picopass_scene_device_info_on_event(void* context, SceneManagerEvent event)
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
consumed = true;
consumed = scene_manager_previous_scene(picopass->scene_manager);
}
return consumed;
}
Expand Down
1 change: 1 addition & 0 deletions base_pack/picopass/scenes/picopass_scene_loclass.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ void picopass_scene_loclass_on_enter(void* context) {

loclass_set_callback(picopass->loclass, picopass_loclass_result_callback, picopass);
loclass_set_header(picopass->loclass, "Loclass");
loclass_set_subheader(picopass->loclass, "Hold To Reader");

picopass_blink_emulate_start(picopass);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoclass);
Expand Down
8 changes: 6 additions & 2 deletions base_pack/picopass/scenes/picopass_scene_more_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ void picopass_scene_more_info_on_enter(void* context) {

for(size_t i = 0; i < app_limit; i++) {
for(size_t j = 0; j < PICOPASS_BLOCK_LEN; j += 2) {
furi_string_cat_printf(
str, "%02X%02X ", card_data[i].data[j], card_data[i].data[j + 1]);
if(card_data[i].valid) {
furi_string_cat_printf(
str, "%02X%02X ", card_data[i].data[j], card_data[i].data[j + 1]);
} else {
furi_string_cat_printf(str, "???? ");
}
}
}

Expand Down
27 changes: 25 additions & 2 deletions base_pack/picopass/scenes/picopass_scene_read_card_success.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include <dolphin/dolphin.h>
#include <picopass_keys.h>

#define TAG "PicopassSceneReadCardSuccess"

void picopass_scene_read_card_success_widget_callback(
GuiButtonType result,
InputType type,
Expand All @@ -27,6 +29,28 @@ void picopass_scene_read_card_success_on_enter(void* context) {
// Send notification
notification_message(picopass->notifications, &sequence_success);

// For initial testing, print auth method
switch(picopass->dev->dev_data.auth) {
case PicopassDeviceAuthMethodUnset:
FURI_LOG_D(TAG, "Auth: Unset");
break;
case PicopassDeviceAuthMethodNone:
FURI_LOG_D(TAG, "Auth: None");
break;
case PicopassDeviceAuthMethodKey:
FURI_LOG_D(TAG, "Auth: Key");
break;
case PicopassDeviceAuthMethodNrMac:
FURI_LOG_D(TAG, "Auth: NR-MAC");
break;
case PicopassDeviceAuthMethodFailed:
FURI_LOG_D(TAG, "Auth: Failed");
break;
default:
FURI_LOG_D(TAG, "Auth: Unknown");
break;
};

// Setup view
PicopassBlock* card_data = picopass->dev->dev_data.card_data;
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
Expand Down Expand Up @@ -133,8 +157,7 @@ void picopass_scene_read_card_success_on_enter(void* context) {
furi_string_cat_printf(credential_str, " +SIO");
}

bool no_key = picopass_is_memset(
card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN);
bool no_key = !card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid;

if(no_key) {
furi_string_cat_printf(key_str, "No Key: used NR-MAC");
Expand Down
Loading

0 comments on commit bf06a6f

Please sign in to comment.