|
|
@@ -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>
|
|
|
|
|
|
@@ -15,6 +16,7 @@ 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));
|
|
|
@@ -169,6 +171,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 {
|
|
|
@@ -203,13 +206,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;
|
|
|
@@ -246,6 +257,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);
|
|
|
@@ -260,26 +284,39 @@ 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];
|
|
|
@@ -287,16 +324,29 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo
|
|
|
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);
|
|
|
@@ -371,10 +421,14 @@ 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->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) {
|
|
|
@@ -450,7 +504,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]));
|