mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-13 14:48:35 -07:00
Temporarily backport app updates from apps repo
This commit is contained in:
@@ -10,13 +10,14 @@ App(
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
fap_description="App to communicate with NFC tags using the PicoPass(iClass) format",
|
||||
fap_version="1.2",
|
||||
fap_version="1.7",
|
||||
fap_icon="125_10px.png",
|
||||
fap_category="NFC",
|
||||
fap_libs=["mbedtls"],
|
||||
fap_private_libs=[
|
||||
Lib(
|
||||
name="loclass",
|
||||
cflags=["-O3"],
|
||||
),
|
||||
],
|
||||
fap_icon_assets="icons",
|
||||
|
||||
@@ -110,12 +110,7 @@ static void init_opt_select_LUT(void) {
|
||||
}
|
||||
***********************************************************************************/
|
||||
|
||||
#define loclass_opt__select(x, y, r) \
|
||||
(4 & ((((r) & ((r) << 2)) >> 5) ^ (((r) & ~((r) << 2)) >> 4) ^ (((r) | (r) << 2) >> 3))) | \
|
||||
(2 & ((((r) | (r) << 2) >> 6) ^ (((r) | (r) << 2) >> 1) ^ ((r) >> 5) ^ (r) ^ (((x) ^ (y)) << 1))) | \
|
||||
(1 & ((((r) & ~((r) << 2)) >> 4) ^ (((r) & ((r) << 2)) >> 3) ^ (r) ^ (x)))
|
||||
|
||||
static void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y) {
|
||||
static inline void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y) {
|
||||
uint16_t Tt = s->t & 0xc533;
|
||||
Tt = Tt ^ (Tt >> 1);
|
||||
Tt = Tt ^ (Tt >> 4);
|
||||
@@ -133,16 +128,15 @@ static void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y
|
||||
s->b = s->b >> 1;
|
||||
s->b |= (opt_B ^ s->r) << 7;
|
||||
|
||||
uint8_t opt_select = loclass_opt_select_LUT[s->r] & 0x04;
|
||||
opt_select |= (loclass_opt_select_LUT[s->r] ^ ((Tt ^ y) << 1)) & 0x02;
|
||||
opt_select |= (loclass_opt_select_LUT[s->r] ^ Tt) & 0x01;
|
||||
uint8_t Tt1 = Tt & 0x01;
|
||||
uint8_t opt_select = loclass_opt_select_LUT[s->r] ^ Tt1 ^ ((Tt1 ^ (y & 0x01)) << 1);
|
||||
|
||||
uint8_t r = s->r;
|
||||
s->r = (k[opt_select] ^ s->b) + s->l;
|
||||
s->l = s->r + r;
|
||||
}
|
||||
|
||||
static void loclass_opt_suc(
|
||||
static inline void loclass_opt_suc(
|
||||
const uint8_t* k,
|
||||
LoclassState_t* s,
|
||||
const uint8_t* in,
|
||||
@@ -150,21 +144,22 @@ static void loclass_opt_suc(
|
||||
bool add32Zeroes) {
|
||||
for(int i = 0; i < length; i++) {
|
||||
uint8_t head = in[i];
|
||||
#pragma GCC unroll 8
|
||||
for(int j = 0; j < 8; j++) {
|
||||
loclass_opt_successor(k, s, head);
|
||||
head >>= 1;
|
||||
}
|
||||
}
|
||||
//For tag MAC, an additional 32 zeroes
|
||||
// For tag MAC, an additional 32 zeroes
|
||||
if(add32Zeroes) {
|
||||
for(int i = 0; i < 16; i++) {
|
||||
loclass_opt_successor(k, s, 0);
|
||||
for(int i = 0; i < 32; i++) {
|
||||
loclass_opt_successor(k, s, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void loclass_opt_output(const uint8_t* k, LoclassState_t* s, uint8_t* buffer) {
|
||||
static inline void loclass_opt_output(const uint8_t* k, LoclassState_t* s, uint8_t* buffer) {
|
||||
#pragma GCC unroll 4
|
||||
for(uint8_t times = 0; times < 4; times++) {
|
||||
uint8_t bout = 0;
|
||||
bout |= (s->r & 0x4) >> 2;
|
||||
@@ -280,22 +275,32 @@ void loclass_opt_doTagMAC_2(
|
||||
loclass_opt_output(div_key_p, &_init, mac);
|
||||
}
|
||||
|
||||
/**
|
||||
* The second part of the tag MAC calculation, since the CC is already calculated into the state,
|
||||
* this function is fed only the NR, and generates both the reader and tag MACs.
|
||||
* @param _init - precalculated cipher state
|
||||
* @param nr - the reader challenge
|
||||
* @param rmac - where to store the reader MAC
|
||||
* @param tmac - where to store the tag MAC
|
||||
* @param div_key_p - the key to use
|
||||
*/
|
||||
void loclass_opt_doBothMAC_2(
|
||||
LoclassState_t _init,
|
||||
uint8_t* nr,
|
||||
uint8_t rmac[4],
|
||||
uint8_t tmac[4],
|
||||
const uint8_t* div_key_p) {
|
||||
loclass_opt_suc(div_key_p, &_init, nr, 4, false);
|
||||
// Save internal state for reuse before outputting
|
||||
LoclassState_t nr_state = _init;
|
||||
loclass_opt_output(div_key_p, &_init, rmac);
|
||||
// Feed the 32 0 bits for the tag mac
|
||||
loclass_opt_suc(div_key_p, &nr_state, NULL, 0, true);
|
||||
loclass_opt_output(div_key_p, &nr_state, tmac);
|
||||
LoclassState_t* s = &_init;
|
||||
loclass_opt_suc(div_key_p, s, nr, 4, false);
|
||||
loclass_opt_output(div_key_p, s, rmac);
|
||||
loclass_opt_output(div_key_p, s, tmac);
|
||||
}
|
||||
|
||||
void loclass_iclass_calc_div_key(uint8_t* csn, const uint8_t* key, uint8_t* div_key, bool elite) {
|
||||
void loclass_iclass_calc_div_key(
|
||||
const uint8_t* csn,
|
||||
const uint8_t* key,
|
||||
uint8_t* div_key,
|
||||
bool elite) {
|
||||
if(elite) {
|
||||
uint8_t keytable[128] = {0};
|
||||
uint8_t key_index[8] = {0};
|
||||
|
||||
@@ -109,5 +109,9 @@ void loclass_opt_doBothMAC_2(
|
||||
const uint8_t* div_key_p);
|
||||
|
||||
void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]);
|
||||
void loclass_iclass_calc_div_key(uint8_t* csn, const uint8_t* key, uint8_t* div_key, bool elite);
|
||||
void loclass_iclass_calc_div_key(
|
||||
const uint8_t* csn,
|
||||
const uint8_t* key,
|
||||
uint8_t* div_key,
|
||||
bool elite);
|
||||
#endif // OPTIMIZED_CIPHER_H
|
||||
|
||||
@@ -302,7 +302,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) {
|
||||
* @param key
|
||||
* @param div_key
|
||||
*/
|
||||
void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
|
||||
void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
|
||||
mbedtls_des_context loclass_ctx_enc;
|
||||
|
||||
// Prepare the DES key
|
||||
|
||||
@@ -56,7 +56,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]);
|
||||
* @param div_key
|
||||
*/
|
||||
|
||||
void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key);
|
||||
void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key);
|
||||
/**
|
||||
* @brief Permutes a key from standard NIST format to Iclass specific format
|
||||
* @param key
|
||||
|
||||
@@ -38,6 +38,8 @@ void loclass_writer_free(LoclassWriter* instance) {
|
||||
}
|
||||
|
||||
bool loclass_writer_write_start_stop(LoclassWriter* instance, bool start) {
|
||||
furi_assert(instance != NULL);
|
||||
|
||||
FuriHalRtcDateTime curr_dt;
|
||||
furi_hal_rtc_get_datetime(&curr_dt);
|
||||
uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||
@@ -97,4 +99,4 @@ bool loclass_writer_write_params(
|
||||
bool write_success = stream_write_string(instance->file_stream, str);
|
||||
furi_string_free(str);
|
||||
return write_success;
|
||||
}
|
||||
}
|
||||
|
||||
11
applications/external/picopass/picopass.c
vendored
11
applications/external/picopass/picopass.c
vendored
@@ -23,7 +23,6 @@ void picopass_tick_event_callback(void* context) {
|
||||
Picopass* picopass_alloc() {
|
||||
Picopass* picopass = malloc(sizeof(Picopass));
|
||||
|
||||
picopass->worker = picopass_worker_alloc();
|
||||
picopass->view_dispatcher = view_dispatcher_alloc();
|
||||
picopass->scene_manager = scene_manager_alloc(&picopass_scene_handlers, picopass);
|
||||
view_dispatcher_enable_queue(picopass->view_dispatcher);
|
||||
@@ -35,6 +34,8 @@ Picopass* picopass_alloc() {
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
picopass->view_dispatcher, picopass_tick_event_callback, 100);
|
||||
|
||||
picopass->nfc = nfc_alloc();
|
||||
|
||||
// Picopass device
|
||||
picopass->dev = picopass_device_alloc();
|
||||
|
||||
@@ -100,6 +101,8 @@ void picopass_free(Picopass* picopass) {
|
||||
picopass_device_free(picopass->dev);
|
||||
picopass->dev = NULL;
|
||||
|
||||
nfc_free(picopass->nfc);
|
||||
|
||||
// Submenu
|
||||
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu);
|
||||
submenu_free(picopass->submenu);
|
||||
@@ -130,10 +133,6 @@ void picopass_free(Picopass* picopass) {
|
||||
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoclass);
|
||||
loclass_free(picopass->loclass);
|
||||
|
||||
// Worker
|
||||
picopass_worker_stop(picopass->worker);
|
||||
picopass_worker_free(picopass->worker);
|
||||
|
||||
// View Dispatcher
|
||||
view_dispatcher_free(picopass->view_dispatcher);
|
||||
|
||||
@@ -210,7 +209,7 @@ void picopass_show_loading_popup(void* context, bool show) {
|
||||
|
||||
static void picopass_migrate_from_old_folder() {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_common_migrate(storage, EXT_PATH("picopass"), STORAGE_APP_DATA_PATH_PREFIX);
|
||||
storage_common_migrate(storage, "/ext/picopass", STORAGE_APP_DATA_PATH_PREFIX);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
|
||||
196
applications/external/picopass/picopass_device.c
vendored
196
applications/external/picopass/picopass_device.c
vendored
@@ -2,7 +2,12 @@
|
||||
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include "picopass_icons.h"
|
||||
#include <picopass_icons.h>
|
||||
|
||||
#include <toolbox/protocols/protocol_dict.h>
|
||||
#include <lfrfid/protocols/lfrfid_protocols.h>
|
||||
#include <lfrfid/lfrfid_dict_file.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "PicopassDevice"
|
||||
@@ -31,6 +36,70 @@ void picopass_device_set_name(PicopassDevice* dev, const char* name) {
|
||||
strlcpy(dev->dev_name, name, PICOPASS_DEV_NAME_MAX_LEN);
|
||||
}
|
||||
|
||||
static bool picopass_device_save_file_lfrfid(PicopassDevice* dev, FuriString* file_path) {
|
||||
furi_assert(dev);
|
||||
PicopassPacs* pacs = &dev->dev_data.pacs;
|
||||
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
|
||||
ProtocolId protocol = LFRFIDProtocolHidGeneric;
|
||||
|
||||
bool result = false;
|
||||
uint64_t target = 0;
|
||||
uint64_t sentinel = 1ULL << pacs->bitLength;
|
||||
memcpy(&target, pacs->credential, PICOPASS_BLOCK_LEN);
|
||||
target = __builtin_bswap64(target);
|
||||
FURI_LOG_D(TAG, "Original (%d): %016llx", pacs->bitLength, target);
|
||||
|
||||
if(pacs->bitLength == 26) {
|
||||
//3 bytes
|
||||
protocol = LFRFIDProtocolH10301;
|
||||
// Remove parity
|
||||
target = (target >> 1) & 0xFFFFFF;
|
||||
// Reverse order since it'll get reversed again
|
||||
target = __builtin_bswap64(target) >> (64 - 24);
|
||||
} else if(pacs->bitLength < 44) {
|
||||
// https://gist.github.com/blark/e8f125e402f576bdb7e2d7b3428bdba6
|
||||
protocol = LFRFIDProtocolHidGeneric;
|
||||
if(pacs->bitLength <= 36) {
|
||||
uint64_t header = 1ULL << 37;
|
||||
target = __builtin_bswap64((target | sentinel | header) << 4) >> (64 - 48);
|
||||
} else {
|
||||
target = __builtin_bswap64((target | sentinel) << 4) >> (64 - 48);
|
||||
}
|
||||
} else {
|
||||
//8 bytes
|
||||
protocol = LFRFIDProtocolHidExGeneric;
|
||||
target = __builtin_bswap64(target);
|
||||
}
|
||||
|
||||
size_t data_size = protocol_dict_get_data_size(dict, protocol);
|
||||
uint8_t* data = malloc(data_size);
|
||||
if(data_size < 8) {
|
||||
memcpy(data, (void*)&target, data_size);
|
||||
} else {
|
||||
// data_size 12 for LFRFIDProtocolHidExGeneric
|
||||
memcpy(data + 4, (void*)&target, 8);
|
||||
}
|
||||
|
||||
protocol_dict_set_data(dict, protocol, data, data_size);
|
||||
free(data);
|
||||
|
||||
FuriString* briefStr;
|
||||
briefStr = furi_string_alloc();
|
||||
protocol_dict_render_brief_data(dict, briefStr, protocol);
|
||||
FURI_LOG_D(TAG, "LFRFID Brief: %s", furi_string_get_cstr(briefStr));
|
||||
furi_string_free(briefStr);
|
||||
|
||||
result = lfrfid_dict_file_save(dict, protocol, furi_string_get_cstr(file_path));
|
||||
if(result) {
|
||||
FURI_LOG_D(TAG, "Written: %d", result);
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Failed to write");
|
||||
}
|
||||
|
||||
protocol_dict_free(dict);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool picopass_device_save_file(
|
||||
PicopassDevice* dev,
|
||||
const char* dev_name,
|
||||
@@ -38,6 +107,7 @@ static bool picopass_device_save_file(
|
||||
const char* extension,
|
||||
bool use_load_path) {
|
||||
furi_assert(dev);
|
||||
FURI_LOG_D(TAG, "Save File");
|
||||
|
||||
bool saved = false;
|
||||
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
|
||||
@@ -56,30 +126,17 @@ static bool picopass_device_save_file(
|
||||
// First remove picopass device file if it was saved
|
||||
furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
|
||||
}
|
||||
// Open file
|
||||
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
|
||||
|
||||
if(dev->format == PicopassDeviceSaveFormatHF) {
|
||||
uint32_t fc = pacs->record.FacilityCode;
|
||||
uint32_t cn = pacs->record.CardNumber;
|
||||
// Open file
|
||||
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
|
||||
|
||||
// Write header
|
||||
if(!flipper_format_write_header_cstr(file, picopass_file_header, picopass_file_version))
|
||||
break;
|
||||
if(pacs->record.valid) {
|
||||
if(!flipper_format_write_uint32(file, "Facility Code", &fc, 1)) break;
|
||||
if(!flipper_format_write_uint32(file, "Card Number", &cn, 1)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Credential", pacs->credential, RFAL_PICOPASS_BLOCK_LEN))
|
||||
break;
|
||||
if(pacs->pin_length > 0) {
|
||||
if(!flipper_format_write_hex(
|
||||
file, "PIN\t\t", pacs->pin0, RFAL_PICOPASS_BLOCK_LEN))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "PIN(cont.)\t", pacs->pin1, RFAL_PICOPASS_BLOCK_LEN))
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!flipper_format_write_hex(file, "Credential", pacs->credential, PICOPASS_BLOCK_LEN))
|
||||
break;
|
||||
|
||||
// TODO: Add elite
|
||||
if(!flipper_format_write_comment_cstr(file, "Picopass blocks")) break;
|
||||
bool block_saved = true;
|
||||
@@ -90,30 +147,14 @@ static bool picopass_device_save_file(
|
||||
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),
|
||||
AA1[i].data,
|
||||
RFAL_PICOPASS_BLOCK_LEN)) {
|
||||
file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) {
|
||||
block_saved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!block_saved) break;
|
||||
} else if(dev->format == PicopassDeviceSaveFormatLF) {
|
||||
const char* lf_header = "Flipper RFID key";
|
||||
// Write header
|
||||
if(!flipper_format_write_header_cstr(file, lf_header, 1)) break;
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
file,
|
||||
"This was generated from the Picopass plugin and may not match current lfrfid"))
|
||||
break;
|
||||
// When lfrfid supports more formats, update this
|
||||
if(!flipper_format_write_string_cstr(file, "Key type", "H10301")) break;
|
||||
uint8_t H10301[3] = {0};
|
||||
H10301[0] = pacs->record.FacilityCode;
|
||||
H10301[1] = pacs->record.CardNumber >> 8;
|
||||
H10301[2] = pacs->record.CardNumber & 0x00FF;
|
||||
if(!flipper_format_write_hex(file, "Data", H10301, 3)) break;
|
||||
saved = picopass_device_save_file_lfrfid(dev, temp_str);
|
||||
}
|
||||
saved = true;
|
||||
} while(0);
|
||||
@@ -131,7 +172,7 @@ 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, EXT_PATH("lfrfid"), ".rfid", true);
|
||||
return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -167,7 +208,7 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo
|
||||
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), AA1[i].data, RFAL_PICOPASS_BLOCK_LEN)) {
|
||||
file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) {
|
||||
block_read = false;
|
||||
break;
|
||||
}
|
||||
@@ -179,15 +220,15 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo
|
||||
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), AA1[i].data, RFAL_PICOPASS_BLOCK_LEN)) {
|
||||
file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) {
|
||||
block_read = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!block_read) break;
|
||||
|
||||
if(picopass_device_parse_credential(AA1, pacs) != ERR_NONE) break;
|
||||
if(picopass_device_parse_wiegand(pacs->credential, &pacs->record) != ERR_NONE) break;
|
||||
picopass_device_parse_credential(AA1, pacs);
|
||||
picopass_device_parse_wiegand(pacs->credential, pacs);
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
@@ -304,7 +345,7 @@ void picopass_device_set_loading_callback(
|
||||
dev->loading_cb_ctx = context;
|
||||
}
|
||||
|
||||
ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
|
||||
void picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
|
||||
uint8_t key[32] = {0};
|
||||
memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey));
|
||||
mbedtls_des3_context ctx;
|
||||
@@ -312,40 +353,25 @@ ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
|
||||
mbedtls_des3_set2key_dec(&ctx, key);
|
||||
mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
|
||||
mbedtls_des3_free(&ctx);
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) {
|
||||
ReturnCode err;
|
||||
|
||||
void picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) {
|
||||
pacs->biometrics = AA1[6].data[4];
|
||||
pacs->pin_length = AA1[6].data[6] & 0x0F;
|
||||
pacs->encryption = AA1[6].data[7];
|
||||
|
||||
if(pacs->encryption == PicopassDeviceEncryption3DES) {
|
||||
FURI_LOG_D(TAG, "3DES Encrypted");
|
||||
err = picopass_device_decrypt(AA1[7].data, pacs->credential);
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||
return err;
|
||||
}
|
||||
picopass_device_decrypt(AA1[7].data, pacs->credential);
|
||||
|
||||
err = picopass_device_decrypt(AA1[8].data, pacs->pin0);
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||
return err;
|
||||
}
|
||||
picopass_device_decrypt(AA1[8].data, pacs->pin0);
|
||||
|
||||
err = picopass_device_decrypt(AA1[9].data, pacs->pin1);
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||
return err;
|
||||
}
|
||||
picopass_device_decrypt(AA1[9].data, pacs->pin1);
|
||||
} else if(pacs->encryption == PicopassDeviceEncryptionNone) {
|
||||
FURI_LOG_D(TAG, "No Encryption");
|
||||
memcpy(pacs->credential, AA1[7].data, RFAL_PICOPASS_BLOCK_LEN);
|
||||
memcpy(pacs->pin0, AA1[8].data, RFAL_PICOPASS_BLOCK_LEN);
|
||||
memcpy(pacs->pin1, AA1[9].data, RFAL_PICOPASS_BLOCK_LEN);
|
||||
memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN);
|
||||
memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN);
|
||||
memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN);
|
||||
} else if(pacs->encryption == PicopassDeviceEncryptionDES) {
|
||||
FURI_LOG_D(TAG, "DES Encrypted");
|
||||
} else {
|
||||
@@ -353,41 +379,33 @@ ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pa
|
||||
}
|
||||
|
||||
pacs->sio = (AA1[10].data[0] == 0x30); // rough check
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
ReturnCode picopass_device_parse_wiegand(uint8_t* credential, PicopassWiegandRecord* record) {
|
||||
void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs) {
|
||||
uint32_t* halves = (uint32_t*)credential;
|
||||
if(halves[0] == 0) {
|
||||
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
|
||||
record->bitLength = 31 - leading0s;
|
||||
pacs->bitLength = 31 - leading0s;
|
||||
} else {
|
||||
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0]));
|
||||
record->bitLength = 63 - leading0s;
|
||||
pacs->bitLength = 63 - leading0s;
|
||||
}
|
||||
FURI_LOG_D(TAG, "bitLength: %d", record->bitLength);
|
||||
|
||||
// Remove sentinel bit from credential. Byteswapping to handle array of bytes vs 64bit value
|
||||
uint64_t sentinel = __builtin_bswap64(1ULL << record->bitLength);
|
||||
uint64_t sentinel = __builtin_bswap64(1ULL << pacs->bitLength);
|
||||
uint64_t swapped = 0;
|
||||
memcpy(&swapped, credential, sizeof(uint64_t));
|
||||
swapped = swapped ^ sentinel;
|
||||
memcpy(credential, &swapped, sizeof(uint64_t));
|
||||
FURI_LOG_D(TAG, "PACS: (%d) %016llx", record->bitLength, swapped);
|
||||
|
||||
if(record->bitLength == 26) {
|
||||
uint8_t* v4 = credential + 4;
|
||||
uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24);
|
||||
|
||||
record->CardNumber = (bot >> 1) & 0xFFFF;
|
||||
record->FacilityCode = (bot >> 17) & 0xFF;
|
||||
FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber);
|
||||
record->valid = true;
|
||||
} else {
|
||||
record->CardNumber = 0;
|
||||
record->FacilityCode = 0;
|
||||
record->valid = false;
|
||||
}
|
||||
return ERR_NONE;
|
||||
FURI_LOG_D(TAG, "PACS: (%d) %016llx", pacs->bitLength, swapped);
|
||||
}
|
||||
|
||||
bool picopass_device_hid_csn(PicopassDevice* dev) {
|
||||
furi_assert(dev);
|
||||
PicopassBlock* AA1 = dev->dev_data.AA1;
|
||||
uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data;
|
||||
// From Proxmark3 RRG sourcecode
|
||||
bool isHidRange = (memcmp(csn + 5, "\xFF\x12\xE0", 3) == 0) && ((csn[4] & 0xF0) == 0xF0);
|
||||
|
||||
return isHidRange;
|
||||
}
|
||||
|
||||
29
applications/external/picopass/picopass_device.h
vendored
29
applications/external/picopass/picopass_device.h
vendored
@@ -12,6 +12,13 @@
|
||||
#include <optimized_cipher.h>
|
||||
#include "helpers/iclass_elite_dict.h"
|
||||
|
||||
#define LOCLASS_NUM_CSNS 9
|
||||
#ifndef LOCLASS_NUM_PER_CSN
|
||||
// Collect 2 MACs per CSN to account for keyroll modes by default
|
||||
#define LOCLASS_NUM_PER_CSN 2
|
||||
#endif
|
||||
#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN)
|
||||
|
||||
#define PICOPASS_DEV_NAME_MAX_LEN 22
|
||||
#define PICOPASS_READER_DATA_MAX_SIZE 64
|
||||
#define PICOPASS_MAX_APP_LIMIT 32
|
||||
@@ -38,8 +45,9 @@
|
||||
// Read Access, 1 meanns anonymous read enabled, 0 means must auth to read applicaion
|
||||
#define PICOPASS_FUSE_RA 0x01
|
||||
|
||||
#define PICOPASS_APP_FOLDER EXT_PATH("picopass")
|
||||
#define PICOPASS_APP_FOLDER ANY_PATH("picopass")
|
||||
#define PICOPASS_APP_EXTENSION ".picopass"
|
||||
#define PICOPASS_APP_FILE_PREFIX "Picopass"
|
||||
#define PICOPASS_APP_SHADOW_EXTENSION ".pas"
|
||||
|
||||
#define PICOPASS_DICT_KEY_BATCH_SIZE 10
|
||||
@@ -69,15 +77,9 @@ typedef enum {
|
||||
PicopassEmulatorStateIdle,
|
||||
PicopassEmulatorStateActive,
|
||||
PicopassEmulatorStateSelected,
|
||||
PicopassEmulatorStateStopEmulation,
|
||||
} PicopassEmulatorState;
|
||||
|
||||
typedef struct {
|
||||
bool valid;
|
||||
uint8_t bitLength;
|
||||
uint8_t FacilityCode;
|
||||
uint16_t CardNumber;
|
||||
} PicopassWiegandRecord;
|
||||
|
||||
typedef struct {
|
||||
bool legacy;
|
||||
bool se_enabled;
|
||||
@@ -87,20 +89,19 @@ typedef struct {
|
||||
bool elite_kdf;
|
||||
uint8_t pin_length;
|
||||
PicopassEncryption encryption;
|
||||
uint8_t bitLength;
|
||||
uint8_t credential[8];
|
||||
uint8_t pin0[8];
|
||||
uint8_t pin1[8];
|
||||
PicopassWiegandRecord record;
|
||||
} PicopassPacs;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[RFAL_PICOPASS_BLOCK_LEN];
|
||||
uint8_t data[PICOPASS_BLOCK_LEN];
|
||||
} PicopassBlock;
|
||||
|
||||
typedef struct {
|
||||
PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT];
|
||||
PicopassPacs pacs;
|
||||
IclassEliteDictAttackData iclass_elite_dict_attack_data;
|
||||
} PicopassDeviceData;
|
||||
|
||||
typedef struct {
|
||||
@@ -109,6 +110,7 @@ typedef struct {
|
||||
uint8_t key_block_num; // in loclass mode used to store csn#
|
||||
bool loclass_mode;
|
||||
bool loclass_got_std_key;
|
||||
uint8_t loclass_mac_buffer[8 * LOCLASS_NUM_PER_CSN];
|
||||
LoclassWriter* loclass_writer;
|
||||
} PicopassEmulatorCtx;
|
||||
|
||||
@@ -144,5 +146,6 @@ void picopass_device_set_loading_callback(
|
||||
PicopassLoadingCallback callback,
|
||||
void* context);
|
||||
|
||||
ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs);
|
||||
ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record);
|
||||
void picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs);
|
||||
void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs);
|
||||
bool picopass_device_hid_csn(PicopassDevice* dev);
|
||||
|
||||
49
applications/external/picopass/picopass_i.h
vendored
49
applications/external/picopass/picopass_i.h
vendored
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "picopass.h"
|
||||
#include "picopass_worker.h"
|
||||
#include "picopass_device.h"
|
||||
|
||||
#include "rfal_picopass.h"
|
||||
@@ -27,14 +26,20 @@
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include "picopass_icons.h"
|
||||
#include <picopass_icons.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#include <nfc/nfc.h>
|
||||
#include <nfc/helpers/nfc_dict.h>
|
||||
#include "protocol/picopass_poller.h"
|
||||
#include "protocol/picopass_listener.h"
|
||||
|
||||
#define PICOPASS_TEXT_STORE_SIZE 128
|
||||
|
||||
#define LOCLASS_NUM_CSNS 9
|
||||
// Collect 2 MACs per CSN to account for keyroll modes
|
||||
#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * 2)
|
||||
#define PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt")
|
||||
#define PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_standard_dict.txt")
|
||||
#define PICOPASS_ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt")
|
||||
|
||||
enum PicopassCustomEvent {
|
||||
// Reserve first 100 events for button types and indexes, starting from 0
|
||||
@@ -45,6 +50,12 @@ enum PicopassCustomEvent {
|
||||
PicopassCustomEventByteInputDone,
|
||||
PicopassCustomEventTextInputDone,
|
||||
PicopassCustomEventDictAttackSkip,
|
||||
PicopassCustomEventDictAttackUpdateView,
|
||||
PicopassCustomEventLoclassGotMac,
|
||||
PicopassCustomEventLoclassGotStandardKey,
|
||||
|
||||
PicopassCustomEventPollerSuccess,
|
||||
PicopassCustomEventPollerFail,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
@@ -52,17 +63,37 @@ typedef enum {
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
uint16_t total_keys;
|
||||
uint16_t current_key;
|
||||
bool card_detected;
|
||||
} PicopassDictAttackContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t key_to_write[PICOPASS_BLOCK_LEN];
|
||||
bool is_elite;
|
||||
} PicopassWriteKeyContext;
|
||||
|
||||
typedef struct {
|
||||
size_t macs_collected;
|
||||
} PicopassLoclassContext;
|
||||
|
||||
struct Picopass {
|
||||
PicopassWorker* worker;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Gui* gui;
|
||||
NotificationApp* notifications;
|
||||
SceneManager* scene_manager;
|
||||
PicopassDevice* dev;
|
||||
|
||||
Nfc* nfc;
|
||||
PicopassPoller* poller;
|
||||
PicopassListener* listener;
|
||||
NfcDict* dict;
|
||||
|
||||
char text_store[PICOPASS_TEXT_STORE_SIZE + 1];
|
||||
FuriString* text_box_store;
|
||||
uint8_t byte_input_store[RFAL_PICOPASS_BLOCK_LEN];
|
||||
uint8_t byte_input_store[PICOPASS_BLOCK_LEN];
|
||||
|
||||
// Common Views
|
||||
Submenu* submenu;
|
||||
@@ -73,6 +104,10 @@ struct Picopass {
|
||||
Widget* widget;
|
||||
DictAttack* dict_attack;
|
||||
Loclass* loclass;
|
||||
|
||||
PicopassDictAttackContext dict_attack_ctx;
|
||||
PicopassWriteKeyContext write_key_context;
|
||||
PicopassLoclassContext loclass_context;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
||||
12
applications/external/picopass/picopass_keys.h
vendored
12
applications/external/picopass/picopass_keys.h
vendored
@@ -2,9 +2,9 @@
|
||||
|
||||
#include "picopass_device.h"
|
||||
|
||||
extern const uint8_t picopass_iclass_key[RFAL_PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_factory_credit_key[RFAL_PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_factory_debit_key[RFAL_PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_xice_key[RFAL_PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_xicl_key[RFAL_PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_xics_key[RFAL_PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_iclass_key[PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_factory_credit_key[PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_factory_debit_key[PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_xice_key[PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_xicl_key[PICOPASS_BLOCK_LEN];
|
||||
extern const uint8_t picopass_xics_key[PICOPASS_BLOCK_LEN];
|
||||
|
||||
1275
applications/external/picopass/picopass_worker.c
vendored
1275
applications/external/picopass/picopass_worker.c
vendored
File diff suppressed because it is too large
Load Diff
56
applications/external/picopass/picopass_worker.h
vendored
56
applications/external/picopass/picopass_worker.h
vendored
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "picopass_device.h"
|
||||
#include "picopass_keys.h"
|
||||
|
||||
typedef struct PicopassWorker PicopassWorker;
|
||||
|
||||
typedef enum {
|
||||
// Init states
|
||||
PicopassWorkerStateNone,
|
||||
PicopassWorkerStateBroken,
|
||||
PicopassWorkerStateReady,
|
||||
// Main worker states
|
||||
PicopassWorkerStateDetect,
|
||||
PicopassWorkerStateWrite,
|
||||
PicopassWorkerStateWriteKey,
|
||||
PicopassWorkerStateEliteDictAttack,
|
||||
PicopassWorkerStateEmulate,
|
||||
PicopassWorkerStateLoclass,
|
||||
// Transition
|
||||
PicopassWorkerStateStop,
|
||||
} PicopassWorkerState;
|
||||
|
||||
typedef enum {
|
||||
// Reserve first 50 events for application events
|
||||
PicopassWorkerEventReserved = 50,
|
||||
|
||||
// Picopass worker common events
|
||||
PicopassWorkerEventSuccess,
|
||||
PicopassWorkerEventFail,
|
||||
PicopassWorkerEventNoCardDetected,
|
||||
PicopassWorkerEventSeEnabled,
|
||||
PicopassWorkerEventAborted,
|
||||
PicopassWorkerEventCardDetected,
|
||||
PicopassWorkerEventNewDictKeyBatch,
|
||||
PicopassWorkerEventNoDictFound,
|
||||
PicopassWorkerEventLoclassGotMac,
|
||||
PicopassWorkerEventLoclassGotStandardKey,
|
||||
} PicopassWorkerEvent;
|
||||
|
||||
typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context);
|
||||
|
||||
PicopassWorker* picopass_worker_alloc();
|
||||
|
||||
PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker);
|
||||
|
||||
void picopass_worker_free(PicopassWorker* picopass_worker);
|
||||
|
||||
void picopass_worker_start(
|
||||
PicopassWorker* picopass_worker,
|
||||
PicopassWorkerState state,
|
||||
PicopassDeviceData* dev_data,
|
||||
PicopassWorkerCallback callback,
|
||||
void* context);
|
||||
|
||||
void picopass_worker_stop(PicopassWorker* picopass_worker);
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "picopass_worker.h"
|
||||
#include "loclass_writer.h"
|
||||
#include "picopass_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/toolbox/stream/file_stream.h>
|
||||
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <rfal_rf.h>
|
||||
|
||||
#include <platform.h>
|
||||
|
||||
struct PicopassWorker {
|
||||
FuriThread* thread;
|
||||
Storage* storage;
|
||||
Stream* dict_stream;
|
||||
|
||||
PicopassDeviceData* dev_data;
|
||||
PicopassWorkerCallback callback;
|
||||
void* context;
|
||||
|
||||
PicopassWorkerState state;
|
||||
};
|
||||
|
||||
void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state);
|
||||
|
||||
int32_t picopass_worker_task(void* context);
|
||||
|
||||
void picopass_worker_detect(PicopassWorker* picopass_worker);
|
||||
void picopass_worker_write(PicopassWorker* picopass_worker);
|
||||
void picopass_worker_write_key(PicopassWorker* picopass_worker);
|
||||
void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode);
|
||||
667
applications/external/picopass/protocol/picopass_listener.c
vendored
Normal file
667
applications/external/picopass/protocol/picopass_listener.c
vendored
Normal file
@@ -0,0 +1,667 @@
|
||||
#include "picopass_listener_i.h"
|
||||
#include "picopass_keys.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define PICOPASS_LISTENER_HAS_MASK(x, b) ((x & b) == b)
|
||||
|
||||
typedef enum {
|
||||
PicopassListenerCommandProcessed,
|
||||
PicopassListenerCommandSilent,
|
||||
PicopassListenerCommandSendSoF,
|
||||
PicopassListenerCommandStop,
|
||||
} PicopassListenerCommand;
|
||||
|
||||
typedef PicopassListenerCommand (
|
||||
*PicopassListenerCommandHandler)(PicopassListener* instance, BitBuffer* buf);
|
||||
|
||||
typedef struct {
|
||||
uint8_t start_byte_cmd;
|
||||
size_t cmd_len_bits;
|
||||
PicopassListenerCommandHandler handler;
|
||||
} PicopassListenerCmd;
|
||||
|
||||
// CSNs from Proxmark3 repo
|
||||
static const uint8_t loclass_csns[LOCLASS_NUM_CSNS][PICOPASS_BLOCK_LEN] = {
|
||||
{0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
};
|
||||
|
||||
static void picopass_listener_reset(PicopassListener* instance) {
|
||||
instance->state = PicopassListenerStateIdle;
|
||||
}
|
||||
|
||||
static void picopass_listener_loclass_update_csn(PicopassListener* instance) {
|
||||
// collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
|
||||
const uint8_t* csn =
|
||||
loclass_csns[(instance->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
|
||||
memcpy(instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data, csn, sizeof(PicopassBlock));
|
||||
|
||||
uint8_t key[PICOPASS_BLOCK_LEN] = {};
|
||||
loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
|
||||
memcpy(instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, key, sizeof(PicopassBlock));
|
||||
|
||||
picopass_listener_init_cipher_state_key(instance, key);
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_actall_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
UNUSED(buf);
|
||||
|
||||
if(instance->state != PicopassListenerStateHalt) {
|
||||
instance->state = PicopassListenerStateActive;
|
||||
}
|
||||
// nfc_set_fdt_listen_fc(instance->nfc, 1000);
|
||||
|
||||
return PicopassListenerCommandSendSoF;
|
||||
}
|
||||
|
||||
PicopassListenerCommand picopass_listener_act_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
UNUSED(buf);
|
||||
|
||||
PicopassListenerCommand command = PicopassListenerCommandSendSoF;
|
||||
|
||||
if(instance->state != PicopassListenerStateActive) {
|
||||
command = PicopassListenerCommandSilent;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_halt_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
UNUSED(buf);
|
||||
|
||||
PicopassListenerCommand command = PicopassListenerCommandSendSoF;
|
||||
|
||||
// Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
|
||||
instance->state = PicopassListenerStateIdle;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_identify_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
UNUSED(buf);
|
||||
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if(instance->state != PicopassListenerStateActive) break;
|
||||
picopass_listener_write_anticoll_csn(instance, instance->tx_buffer);
|
||||
PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_D(TAG, "Error sending CSN: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_select_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if((instance->state == PicopassListenerStateHalt) ||
|
||||
(instance->state == PicopassListenerStateIdle)) {
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tmp_buffer,
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
|
||||
sizeof(PicopassBlock));
|
||||
} else {
|
||||
picopass_listener_write_anticoll_csn(instance, instance->tmp_buffer);
|
||||
}
|
||||
const uint8_t* listener_uid = bit_buffer_get_data(instance->tmp_buffer);
|
||||
const uint8_t* received_data = bit_buffer_get_data(buf);
|
||||
|
||||
if(memcmp(listener_uid, &received_data[1], PICOPASS_BLOCK_LEN) != 0) {
|
||||
if(instance->state == PicopassListenerStateActive) {
|
||||
instance->state = PicopassListenerStateIdle;
|
||||
} else if(instance->state == PicopassListenerStateSelected) {
|
||||
// Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
|
||||
instance->state = PicopassListenerStateIdle;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
instance->state = PicopassListenerStateSelected;
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_buffer,
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
|
||||
sizeof(PicopassBlock));
|
||||
|
||||
PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_D(TAG, "Error sending select response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_read_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
uint8_t block_num = bit_buffer_get_byte(buf, 1);
|
||||
if(block_num > PICOPASS_MAX_APP_LIMIT) break;
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) ||
|
||||
(block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
|
||||
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0xff);
|
||||
}
|
||||
} else {
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
|
||||
}
|
||||
PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to tx read block response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static PicopassListenerCommand
|
||||
picopass_listener_readcheck(PicopassListener* instance, BitBuffer* buf, uint8_t key_block_num) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if(instance->state != PicopassListenerStateSelected) break;
|
||||
uint8_t block_num = bit_buffer_get_byte(buf, 1);
|
||||
if(block_num != PICOPASS_SECURE_EPURSE_BLOCK_INDEX) break;
|
||||
|
||||
// loclass mode doesn't do any card side crypto, just logs the readers crypto, so no-op in this mode
|
||||
// we can also no-op if the key block is the same, CHECK re-inits if it failed already
|
||||
if((instance->key_block_num != key_block_num) &&
|
||||
(instance->mode != PicopassListenerModeLoclass)) {
|
||||
instance->key_block_num = key_block_num;
|
||||
picopass_listener_init_cipher_state(instance);
|
||||
}
|
||||
|
||||
// DATA(8)
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
|
||||
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
|
||||
if(error != NfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to tx read check response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_readcheck_kd_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
return picopass_listener_readcheck(instance, buf, PICOPASS_SECURE_KD_BLOCK_INDEX);
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_readcheck_kc_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
return picopass_listener_readcheck(instance, buf, PICOPASS_SECURE_KC_BLOCK_INDEX);
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_check_handler_loclass(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
NfcCommand callback_command = NfcCommandContinue;
|
||||
|
||||
// LOCLASS Reader attack mode
|
||||
do {
|
||||
#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY
|
||||
// loclass mode stores the derived standard debit key in Kd to check
|
||||
|
||||
PicopassBlock key = instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX];
|
||||
uint8_t rmac[4];
|
||||
uint8_t rx_data[9] = {};
|
||||
bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
|
||||
loclass_opt_doReaderMAC_2(instance->cipher_state, &rx_data[1], rmac, key.data);
|
||||
|
||||
if(!memcmp(&rx_data[5], rmac, 4)) {
|
||||
// MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
|
||||
// Either way no point logging it.
|
||||
|
||||
FURI_LOG_W(TAG, "loclass: standard key detected during collection");
|
||||
if(instance->callback) {
|
||||
instance->event.type = PicopassListenerEventTypeLoclassGotStandardKey;
|
||||
callback_command = instance->callback(instance->event, instance->context);
|
||||
if(callback_command == NfcCommandStop) {
|
||||
command = PicopassListenerCommandStop;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't reset the state as the reader may try a different key next without going through anticoll
|
||||
// The reader is always free to redo the anticoll if it wants to anyway
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Save to buffer to defer flushing when we rotate CSN
|
||||
memcpy(
|
||||
instance->loclass_mac_buffer + ((instance->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
|
||||
&rx_data[1],
|
||||
8);
|
||||
|
||||
// Rotate to the next CSN/attempt
|
||||
instance->key_block_num++;
|
||||
|
||||
// CSN changed
|
||||
if(instance->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
|
||||
// Flush NR-MACs for this CSN to SD card
|
||||
for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
|
||||
loclass_writer_write_params(
|
||||
instance->writer,
|
||||
instance->key_block_num + i - LOCLASS_NUM_PER_CSN,
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
|
||||
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data,
|
||||
instance->loclass_mac_buffer + (i * 8),
|
||||
instance->loclass_mac_buffer + (i * 8) + 4);
|
||||
}
|
||||
|
||||
if(instance->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
|
||||
picopass_listener_loclass_update_csn(instance);
|
||||
// Only reset the state when we change to a new CSN for the same reason as when we get a standard key
|
||||
instance->state = PicopassListenerStateIdle;
|
||||
}
|
||||
}
|
||||
if(instance->callback) {
|
||||
instance->event.type = PicopassListenerEventTypeLoclassGotMac;
|
||||
instance->callback(instance->event, instance->context);
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_check_handler_emulation(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
uint8_t rmac[4] = {};
|
||||
uint8_t tmac[4] = {};
|
||||
const uint8_t* key = instance->data->AA1[instance->key_block_num].data;
|
||||
// Since nr isn't const in loclass_opt_doBothMAC_2() copy buffer
|
||||
uint8_t rx_data[9] = {};
|
||||
bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
|
||||
loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);
|
||||
|
||||
#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
|
||||
if(memcmp(&rx_data[5], rmac, 4)) {
|
||||
// Bad MAC from reader, do not send a response.
|
||||
FURI_LOG_I(TAG, "Got bad MAC from reader");
|
||||
// Reset the cipher state since we don't do it in READCHECK
|
||||
picopass_listener_init_cipher_state(instance);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
|
||||
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
|
||||
if(error != NfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed tx update response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
PicopassListenerCommand
|
||||
picopass_listener_check_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if(instance->state != PicopassListenerStateSelected) break;
|
||||
if(instance->mode == PicopassListenerModeLoclass) {
|
||||
command = picopass_listener_check_handler_loclass(instance, buf);
|
||||
} else {
|
||||
command = picopass_listener_check_handler_emulation(instance, buf);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_update_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if(instance->mode == PicopassListenerModeLoclass) break;
|
||||
if(instance->state != PicopassListenerStateSelected) break;
|
||||
|
||||
PicopassBlock config_block = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX];
|
||||
bool pers_mode = PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_PERS);
|
||||
|
||||
const uint8_t* rx_data = bit_buffer_get_data(buf);
|
||||
uint8_t block_num = rx_data[1];
|
||||
if(block_num == PICOPASS_CSN_BLOCK_INDEX) break; // CSN is always read only
|
||||
if(!pers_mode && PICOPASS_LISTENER_HAS_MASK(config_block.data[3], 0x80))
|
||||
break; // Chip is in RO mode, no updated possible (even ePurse)
|
||||
if(!pers_mode && (block_num == PICOPASS_SECURE_AIA_BLOCK_INDEX))
|
||||
break; // AIA can only be set in personalisation mode
|
||||
if(!pers_mode &&
|
||||
((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX ||
|
||||
block_num == PICOPASS_SECURE_KC_BLOCK_INDEX) &&
|
||||
(!PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_CRYPT10))))
|
||||
break;
|
||||
|
||||
if(block_num >= 6 && block_num <= 12) {
|
||||
// bit0 is block6, up to bit6 being block12
|
||||
if(!PICOPASS_LISTENER_HAS_MASK(config_block.data[3], (1 << (block_num - 6)))) {
|
||||
// Block is marked as read-only, deny writing
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO: Check CRC/SIGN depending on if in secure mode
|
||||
// Check correct key
|
||||
// -> Kd only allows decrementing e-Purse
|
||||
// -> per-app controlled by key access config
|
||||
// bool keyAccess = PICOPASS_LISTENER_HAS_MASK(config_block.data[5], 0x01);
|
||||
// -> must auth with that key to change it
|
||||
|
||||
PicopassBlock new_block = {};
|
||||
switch(block_num) {
|
||||
case PICOPASS_CONFIG_BLOCK_INDEX:
|
||||
new_block.data[0] = config_block.data[0]; // Applications Limit
|
||||
new_block.data[1] = config_block.data[1] & rx_data[3]; // OTP
|
||||
new_block.data[2] = config_block.data[2] & rx_data[4]; // OTP
|
||||
new_block.data[3] = config_block.data[3] & rx_data[5]; // Block Write Lock
|
||||
new_block.data[4] = config_block.data[4]; // Chip Config
|
||||
new_block.data[5] = config_block.data[5]; // Memory Config
|
||||
new_block.data[6] = rx_data[8]; // EAS
|
||||
new_block.data[7] = config_block.data[7]; // Fuses
|
||||
|
||||
// Some parts allow w (but not e) if in persMode
|
||||
if(pers_mode) {
|
||||
new_block.data[0] &= rx_data[2]; // Applications Limit
|
||||
new_block.data[4] &= rx_data[6]; // Chip Config
|
||||
new_block.data[5] &= rx_data[7]; // Memory Config
|
||||
new_block.data[7] &= rx_data[9]; // Fuses
|
||||
} else {
|
||||
// Fuses allows setting Crypt1/0 from 1 to 0 only during application mode
|
||||
new_block.data[7] &= rx_data[9] | ~PICOPASS_FUSE_CRYPT10;
|
||||
}
|
||||
break;
|
||||
|
||||
case PICOPASS_SECURE_EPURSE_BLOCK_INDEX:
|
||||
// ePurse updates swap first and second half of the block each update
|
||||
memcpy(&new_block.data[4], &rx_data[2], 4);
|
||||
memcpy(&new_block.data[0], &rx_data[6], 4);
|
||||
break;
|
||||
|
||||
case PICOPASS_SECURE_KD_BLOCK_INDEX:
|
||||
// fallthrough
|
||||
case PICOPASS_SECURE_KC_BLOCK_INDEX:
|
||||
if(!pers_mode) {
|
||||
new_block = instance->data->AA1[block_num];
|
||||
for(size_t i = 0; i < sizeof(PicopassBlock); i++) {
|
||||
new_block.data[i] ^= rx_data[i + 2];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Use default case when in personalisation mode
|
||||
// fallthrough
|
||||
default:
|
||||
memcpy(new_block.data, &rx_data[2], sizeof(PicopassBlock));
|
||||
break;
|
||||
}
|
||||
|
||||
instance->data->AA1[block_num] = new_block;
|
||||
if((block_num == instance->key_block_num) ||
|
||||
(block_num == PICOPASS_SECURE_EPURSE_BLOCK_INDEX)) {
|
||||
picopass_listener_init_cipher_state(instance);
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) ||
|
||||
(block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
|
||||
// Key updates always return FF's
|
||||
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0xff);
|
||||
}
|
||||
} else {
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
|
||||
}
|
||||
|
||||
PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to tx update response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_read4_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if(instance->state != PicopassListenerStateSelected) break;
|
||||
|
||||
uint8_t block_start = bit_buffer_get_byte(buf, 1);
|
||||
if(block_start + 4 >= PICOPASS_MAX_APP_LIMIT) break;
|
||||
|
||||
// TODO: Check CRC?
|
||||
// TODO: Check auth?
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
for(uint8_t i = block_start; i < block_start + 4; i++) {
|
||||
if((i == PICOPASS_SECURE_KD_BLOCK_INDEX) || (i == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
|
||||
for(size_t j = 0; j < sizeof(PicopassBlock); j++) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0xff);
|
||||
}
|
||||
} else {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, instance->data->AA1[i].data, sizeof(PicopassBlock));
|
||||
}
|
||||
}
|
||||
|
||||
PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to tx read4 response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static const PicopassListenerCmd picopass_listener_cmd_handlers[] = {
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_ACTALL,
|
||||
.cmd_len_bits = 8,
|
||||
.handler = picopass_listener_actall_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_ACT,
|
||||
.cmd_len_bits = 8,
|
||||
.handler = picopass_listener_act_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_HALT,
|
||||
.cmd_len_bits = 8,
|
||||
.handler = picopass_listener_halt_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_READ_OR_IDENTIFY,
|
||||
.cmd_len_bits = 8,
|
||||
.handler = picopass_listener_identify_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_SELECT,
|
||||
.cmd_len_bits = 8 * 9,
|
||||
.handler = picopass_listener_select_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_READ_OR_IDENTIFY,
|
||||
.cmd_len_bits = 8 * 4,
|
||||
.handler = picopass_listener_read_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_READCHECK_KD,
|
||||
.cmd_len_bits = 8 * 2,
|
||||
.handler = picopass_listener_readcheck_kd_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_READCHECK_KC,
|
||||
.cmd_len_bits = 8 * 2,
|
||||
.handler = picopass_listener_readcheck_kc_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_CHECK,
|
||||
.cmd_len_bits = 8 * 9,
|
||||
.handler = picopass_listener_check_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_UPDATE,
|
||||
.cmd_len_bits = 8 * 14,
|
||||
.handler = picopass_listener_update_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_READ4,
|
||||
.cmd_len_bits = 8 * 4,
|
||||
.handler = picopass_listener_read4_handler,
|
||||
},
|
||||
};
|
||||
|
||||
PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(data);
|
||||
|
||||
PicopassListener* instance = malloc(sizeof(PicopassListener));
|
||||
instance->nfc = nfc;
|
||||
instance->data = malloc(sizeof(PicopassDeviceData));
|
||||
mempcpy(instance->data, data, sizeof(PicopassDeviceData));
|
||||
|
||||
instance->tx_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX);
|
||||
instance->tmp_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX);
|
||||
|
||||
nfc_set_fdt_listen_fc(instance->nfc, PICOPASS_FDT_LISTEN_FC);
|
||||
nfc_config(instance->nfc, NfcModeListener, NfcTechIso15693);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void picopass_listener_free(PicopassListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->tmp_buffer);
|
||||
free(instance->data);
|
||||
if(instance->writer) {
|
||||
loclass_writer_write_start_stop(instance->writer, false);
|
||||
loclass_writer_free(instance->writer);
|
||||
}
|
||||
free(instance);
|
||||
}
|
||||
|
||||
bool picopass_listener_set_mode(PicopassListener* instance, PicopassListenerMode mode) {
|
||||
furi_assert(instance);
|
||||
bool success = true;
|
||||
|
||||
instance->mode = mode;
|
||||
if(instance->mode == PicopassListenerModeLoclass) {
|
||||
instance->key_block_num = 0;
|
||||
picopass_listener_loclass_update_csn(instance);
|
||||
instance->writer = loclass_writer_alloc();
|
||||
if(instance->writer) {
|
||||
loclass_writer_write_start_stop(instance->writer, true);
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
NfcCommand picopass_listener_start_callback(NfcEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
PicopassListener* instance = context;
|
||||
BitBuffer* rx_buf = event.data.buffer;
|
||||
|
||||
PicopassListenerCommand picopass_cmd = PicopassListenerCommandSilent;
|
||||
if(event.type == NfcEventTypeRxEnd) {
|
||||
for(size_t i = 0; i < COUNT_OF(picopass_listener_cmd_handlers); i++) {
|
||||
if(bit_buffer_get_size(rx_buf) != picopass_listener_cmd_handlers[i].cmd_len_bits) {
|
||||
continue;
|
||||
}
|
||||
if(bit_buffer_get_byte(rx_buf, 0) !=
|
||||
picopass_listener_cmd_handlers[i].start_byte_cmd) {
|
||||
continue;
|
||||
}
|
||||
picopass_cmd = picopass_listener_cmd_handlers[i].handler(instance, rx_buf);
|
||||
break;
|
||||
}
|
||||
if(picopass_cmd == PicopassListenerCommandSendSoF) {
|
||||
nfc_iso15693_listener_tx_sof(instance->nfc);
|
||||
} else if(picopass_cmd == PicopassListenerCommandStop) {
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void picopass_listener_start(
|
||||
PicopassListener* instance,
|
||||
PicopassListenerCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
|
||||
picopass_listener_reset(instance);
|
||||
nfc_start(instance->nfc, picopass_listener_start_callback, instance);
|
||||
}
|
||||
|
||||
void picopass_listener_stop(PicopassListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
nfc_stop(instance->nfc);
|
||||
}
|
||||
|
||||
const PicopassDeviceData* picopass_listener_get_data(PicopassListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
45
applications/external/picopass/protocol/picopass_listener.h
vendored
Normal file
45
applications/external/picopass/protocol/picopass_listener.h
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/nfc.h>
|
||||
#include "picopass_protocol.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
PicopassListenerModeEmulation,
|
||||
PicopassListenerModeLoclass,
|
||||
} PicopassListenerMode;
|
||||
|
||||
typedef enum {
|
||||
PicopassListenerEventTypeLoclassGotStandardKey,
|
||||
PicopassListenerEventTypeLoclassGotMac,
|
||||
} PicopassListenerEventType;
|
||||
|
||||
typedef struct {
|
||||
PicopassListenerEventType type;
|
||||
} PicopassListenerEvent;
|
||||
|
||||
typedef NfcCommand (*PicopassListenerCallback)(PicopassListenerEvent event, void* context);
|
||||
|
||||
typedef struct PicopassListener PicopassListener;
|
||||
|
||||
PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data);
|
||||
|
||||
void picopass_listener_free(PicopassListener* instance);
|
||||
|
||||
bool picopass_listener_set_mode(PicopassListener* instance, PicopassListenerMode mode);
|
||||
|
||||
void picopass_listener_start(
|
||||
PicopassListener* instance,
|
||||
PicopassListenerCallback callback,
|
||||
void* context);
|
||||
|
||||
void picopass_listener_stop(PicopassListener* instance);
|
||||
|
||||
const PicopassDeviceData* picopass_listener_get_data(PicopassListener* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
52
applications/external/picopass/protocol/picopass_listener_i.c
vendored
Normal file
52
applications/external/picopass/protocol/picopass_listener_i.c
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "picopass_listener_i.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
static PicopassError picopass_listener_process_error(NfcError error) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
switch(error) {
|
||||
case NfcErrorNone:
|
||||
ret = PicopassErrorNone;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = PicopassErrorTimeout;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void picopass_listener_init_cipher_state_key(PicopassListener* instance, const uint8_t* key) {
|
||||
uint8_t cc[PICOPASS_BLOCK_LEN] = {};
|
||||
memcpy(
|
||||
cc, instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, sizeof(PicopassBlock));
|
||||
|
||||
instance->cipher_state = loclass_opt_doTagMAC_1(cc, key);
|
||||
}
|
||||
|
||||
void picopass_listener_init_cipher_state(PicopassListener* instance) {
|
||||
uint8_t key[PICOPASS_BLOCK_LEN] = {};
|
||||
memcpy(key, instance->data->AA1[instance->key_block_num].data, sizeof(PicopassBlock));
|
||||
|
||||
picopass_listener_init_cipher_state_key(instance, key);
|
||||
}
|
||||
|
||||
PicopassError picopass_listener_send_frame(PicopassListener* instance, BitBuffer* tx_buffer) {
|
||||
iso13239_crc_append(Iso13239CrcTypePicopass, tx_buffer);
|
||||
NfcError error = nfc_listener_tx(instance->nfc, tx_buffer);
|
||||
|
||||
return picopass_listener_process_error(error);
|
||||
}
|
||||
|
||||
// from proxmark3 armsrc/iclass.c rotateCSN
|
||||
PicopassError picopass_listener_write_anticoll_csn(PicopassListener* instance, BitBuffer* buffer) {
|
||||
const uint8_t* uid = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
|
||||
bit_buffer_reset(buffer);
|
||||
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
bit_buffer_append_byte(buffer, (uid[i] >> 3) | (uid[(i + 1) % 8] << 5));
|
||||
}
|
||||
|
||||
return PicopassErrorNone;
|
||||
}
|
||||
47
applications/external/picopass/protocol/picopass_listener_i.h
vendored
Normal file
47
applications/external/picopass/protocol/picopass_listener_i.h
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "picopass_listener.h"
|
||||
#include <nfc/helpers/iso13239_crc.h>
|
||||
|
||||
#include <optimized_ikeys.h>
|
||||
#include <optimized_cipher.h>
|
||||
#include <loclass_writer.h>
|
||||
|
||||
#define TAG "PicopassListener"
|
||||
|
||||
#define PICOPASS_LISTENER_BUFFER_SIZE_MAX (255)
|
||||
|
||||
typedef enum {
|
||||
PicopassListenerStateIdle,
|
||||
PicopassListenerStateHalt,
|
||||
PicopassListenerStateActive,
|
||||
PicopassListenerStateSelected,
|
||||
} PicopassListenerState;
|
||||
|
||||
struct PicopassListener {
|
||||
Nfc* nfc;
|
||||
PicopassDeviceData* data;
|
||||
PicopassListenerState state;
|
||||
|
||||
LoclassState_t cipher_state;
|
||||
PicopassListenerMode mode;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* tmp_buffer;
|
||||
uint8_t key_block_num;
|
||||
|
||||
LoclassWriter* writer;
|
||||
uint8_t loclass_mac_buffer[8 * LOCLASS_NUM_PER_CSN];
|
||||
|
||||
PicopassListenerEvent event;
|
||||
PicopassListenerCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
void picopass_listener_init_cipher_state_key(PicopassListener* instance, const uint8_t* key);
|
||||
|
||||
void picopass_listener_init_cipher_state(PicopassListener* instance);
|
||||
|
||||
PicopassError picopass_listener_send_frame(PicopassListener* instance, BitBuffer* tx_buffer);
|
||||
|
||||
PicopassError picopass_listener_write_anticoll_csn(PicopassListener* instance, BitBuffer* buffer);
|
||||
543
applications/external/picopass/protocol/picopass_poller.c
vendored
Normal file
543
applications/external/picopass/protocol/picopass_poller.c
vendored
Normal file
@@ -0,0 +1,543 @@
|
||||
#include "picopass_poller_i.h"
|
||||
|
||||
#include "../loclass/optimized_cipher.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define TAG "Picopass"
|
||||
|
||||
typedef NfcCommand (*PicopassPollerStateHandler)(PicopassPoller* instance);
|
||||
|
||||
static void picopass_poller_reset(PicopassPoller* instance) {
|
||||
instance->current_block = 0;
|
||||
}
|
||||
|
||||
static void picopass_poller_prepare_read(PicopassPoller* instance) {
|
||||
instance->app_limit = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] <
|
||||
PICOPASS_MAX_APP_LIMIT ?
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] :
|
||||
PICOPASS_MAX_APP_LIMIT;
|
||||
instance->current_block = 2;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_request_mode_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
instance->event.type = PicopassPollerEventTypeRequestMode;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
instance->mode = instance->event_data.req_mode.mode;
|
||||
instance->state = PicopassPollerStateDetect;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_detect_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
PicopassError error = picopass_poller_actall(instance);
|
||||
|
||||
if(error == PicopassErrorNone) {
|
||||
instance->state = PicopassPollerStateSelect;
|
||||
instance->event.type = PicopassPollerEventTypeCardDetected;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
} else {
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_select_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
do {
|
||||
PicopassError error = picopass_poller_identify(instance, &instance->col_res_serial_num);
|
||||
if(error != PicopassErrorNone) {
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
error =
|
||||
picopass_poller_select(instance, &instance->col_res_serial_num, &instance->serial_num);
|
||||
if(error != PicopassErrorNone) {
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->mode == PicopassPollerModeRead) {
|
||||
instance->state = PicopassPollerStatePreAuth;
|
||||
} else {
|
||||
instance->state = PicopassPollerStateAuth;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
PicopassError error = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
memcpy(
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
|
||||
instance->serial_num.data,
|
||||
sizeof(PicopassSerialNum));
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"csn %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[0],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[1],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[2],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[3],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[4],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[5],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[6],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]);
|
||||
|
||||
PicopassBlock block = {};
|
||||
error = picopass_poller_read_block(instance, 1, &block);
|
||||
if(error != PicopassErrorNone) {
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
memcpy(
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data,
|
||||
block.data,
|
||||
sizeof(PicopassBlock));
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"config %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[1],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[2],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[3],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[4],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[5],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]);
|
||||
|
||||
error = picopass_poller_read_block(instance, 5, &block);
|
||||
if(error != PicopassErrorNone) {
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
memcpy(
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
|
||||
block.data,
|
||||
sizeof(PicopassBlock));
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"aia %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[0],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[1],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[2],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[3],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[4],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[5],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[6],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[7]);
|
||||
|
||||
instance->state = PicopassPollerStateCheckSecurity;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_check_security(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
// Thank you proxmark!
|
||||
PicopassBlock temp_block = {};
|
||||
memset(temp_block.data, 0xff, sizeof(PicopassBlock));
|
||||
instance->data->pacs.legacy =
|
||||
(memcmp(
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
|
||||
temp_block.data,
|
||||
sizeof(PicopassBlock)) == 0);
|
||||
|
||||
temp_block.data[3] = 0x00;
|
||||
temp_block.data[4] = 0x06;
|
||||
instance->data->pacs.se_enabled =
|
||||
(memcmp(
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
|
||||
temp_block.data,
|
||||
sizeof(PicopassBlock)) == 0);
|
||||
|
||||
if(instance->data->pacs.se_enabled) {
|
||||
FURI_LOG_D(TAG, "SE enabled");
|
||||
instance->state = PicopassPollerStateFail;
|
||||
} else {
|
||||
instance->state = PicopassPollerStateAuth;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
do {
|
||||
// Request key
|
||||
instance->event.type = PicopassPollerEventTypeRequestKey;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
if(command != NfcCommandContinue) break;
|
||||
|
||||
if(!instance->event_data.req_key.is_key_provided) {
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Try to %s auth with key %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
instance->event_data.req_key.is_elite_key ? "elite" : "standard",
|
||||
instance->event_data.req_key.key[0],
|
||||
instance->event_data.req_key.key[1],
|
||||
instance->event_data.req_key.key[2],
|
||||
instance->event_data.req_key.key[3],
|
||||
instance->event_data.req_key.key[4],
|
||||
instance->event_data.req_key.key[5],
|
||||
instance->event_data.req_key.key[6],
|
||||
instance->event_data.req_key.key[7]);
|
||||
|
||||
PicopassReadCheckResp read_check_resp = {};
|
||||
uint8_t* csn = instance->serial_num.data;
|
||||
memset(instance->div_key, 0, sizeof(instance->div_key));
|
||||
uint8_t* div_key = NULL;
|
||||
|
||||
if(instance->mode == PicopassPollerModeRead) {
|
||||
div_key = instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
|
||||
} else {
|
||||
div_key = instance->div_key;
|
||||
}
|
||||
|
||||
uint8_t ccnr[12] = {};
|
||||
PicopassMac mac = {};
|
||||
|
||||
PicopassError error = picopass_poller_read_check(instance, &read_check_resp);
|
||||
if(error == PicopassErrorTimeout) {
|
||||
instance->event.type = PicopassPollerEventTypeCardLost;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
instance->state = PicopassPollerStateDetect;
|
||||
break;
|
||||
} else if(error != PicopassErrorNone) {
|
||||
FURI_LOG_E(TAG, "Read check failed: %d", error);
|
||||
break;
|
||||
}
|
||||
memcpy(ccnr, read_check_resp.data, sizeof(PicopassReadCheckResp)); // last 4 bytes left 0
|
||||
|
||||
loclass_iclass_calc_div_key(
|
||||
csn,
|
||||
instance->event_data.req_key.key,
|
||||
div_key,
|
||||
instance->event_data.req_key.is_elite_key);
|
||||
loclass_opt_doReaderMAC(ccnr, div_key, mac.data);
|
||||
|
||||
PicopassCheckResp check_resp = {};
|
||||
error = picopass_poller_check(instance, &mac, &check_resp);
|
||||
if(error == PicopassErrorNone) {
|
||||
FURI_LOG_I(TAG, "Found key");
|
||||
memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
|
||||
if(instance->mode == PicopassPollerModeRead) {
|
||||
memcpy(
|
||||
instance->data->pacs.key, instance->event_data.req_key.key, PICOPASS_KEY_LEN);
|
||||
instance->data->pacs.elite_kdf = instance->event_data.req_key.is_elite_key;
|
||||
picopass_poller_prepare_read(instance);
|
||||
instance->state = PicopassPollerStateReadBlock;
|
||||
} else if(instance->mode == PicopassPollerModeWrite) {
|
||||
instance->state = PicopassPollerStateWriteBlock;
|
||||
} else {
|
||||
instance->state = PicopassPollerStateWriteKey;
|
||||
}
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_read_block_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
do {
|
||||
if(instance->current_block == instance->app_limit) {
|
||||
instance->state = PicopassPollerStateParseCredential;
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX) {
|
||||
// Skip over Kd block which is populated earlier (READ of Kd returns all FF's)
|
||||
instance->current_block++;
|
||||
}
|
||||
|
||||
PicopassBlock block = {};
|
||||
PicopassError error =
|
||||
picopass_poller_read_block(instance, instance->current_block, &block);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_E(TAG, "Failed to read block %d: %d", instance->current_block, error);
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Block %d: %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
instance->current_block,
|
||||
block.data[0],
|
||||
block.data[1],
|
||||
block.data[2],
|
||||
block.data[3],
|
||||
block.data[4],
|
||||
block.data[5],
|
||||
block.data[6],
|
||||
block.data[7]);
|
||||
memcpy(
|
||||
instance->data->AA1[instance->current_block].data, block.data, sizeof(PicopassBlock));
|
||||
instance->current_block++;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_parse_credential_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
picopass_device_parse_credential(instance->data->AA1, &instance->data->pacs);
|
||||
instance->state = PicopassPollerStateParseWiegand;
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_parse_wiegand_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
picopass_device_parse_wiegand(instance->data->pacs.credential, &instance->data->pacs);
|
||||
instance->state = PicopassPollerStateSuccess;
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_write_block_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
PicopassError error = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
instance->event.type = PicopassPollerEventTypeRequestWriteBlock;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
if(command != NfcCommandContinue) break;
|
||||
|
||||
PicopassPollerEventDataRequestWriteBlock* write_block = &instance->event_data.req_write;
|
||||
if(!write_block->perform_write) {
|
||||
instance->state = PicopassPollerStateSuccess;
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Writing %d block", write_block->block_num);
|
||||
uint8_t data[9] = {};
|
||||
data[0] = write_block->block_num;
|
||||
memcpy(&data[1], write_block->block->data, PICOPASS_BLOCK_LEN);
|
||||
loclass_doMAC_N(data, sizeof(data), instance->div_key, instance->mac.data);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
|
||||
write_block->block_num,
|
||||
data[1],
|
||||
data[2],
|
||||
data[3],
|
||||
data[4],
|
||||
data[5],
|
||||
data[6],
|
||||
data[7],
|
||||
data[8],
|
||||
instance->mac.data[0],
|
||||
instance->mac.data[1],
|
||||
instance->mac.data[2],
|
||||
instance->mac.data[3]);
|
||||
error = picopass_poller_write_block(
|
||||
instance, write_block->block_num, write_block->block, &instance->mac);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_E(TAG, "Failed to write block %d. Error %d", write_block->block_num, error);
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_write_key_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
PicopassError error = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
instance->event.type = PicopassPollerEventTypeRequestWriteKey;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
if(command != NfcCommandContinue) break;
|
||||
|
||||
const PicopassDeviceData* picopass_data = instance->event_data.req_write_key.data;
|
||||
const uint8_t* new_key = instance->event_data.req_write_key.key;
|
||||
bool is_elite_key = instance->event_data.req_write_key.is_elite_key;
|
||||
|
||||
const uint8_t* csn = picopass_data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
|
||||
const uint8_t* config_block = picopass_data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data;
|
||||
uint8_t fuses = config_block[7];
|
||||
const uint8_t* old_key = picopass_data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
|
||||
|
||||
PicopassBlock new_block = {};
|
||||
loclass_iclass_calc_div_key(csn, new_key, new_block.data, is_elite_key);
|
||||
|
||||
if((fuses & 0x80) == 0x80) {
|
||||
FURI_LOG_D(TAG, "Plain write for personalized mode key change");
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "XOR write for application mode key change");
|
||||
// XOR when in application mode
|
||||
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
new_block.data[i] ^= old_key[i];
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Writing key to %d block", PICOPASS_SECURE_KD_BLOCK_INDEX);
|
||||
uint8_t data[9] = {};
|
||||
data[0] = PICOPASS_SECURE_KD_BLOCK_INDEX;
|
||||
memcpy(&data[1], new_block.data, PICOPASS_BLOCK_LEN);
|
||||
loclass_doMAC_N(data, sizeof(data), instance->div_key, instance->mac.data);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
|
||||
PICOPASS_SECURE_KD_BLOCK_INDEX,
|
||||
data[1],
|
||||
data[2],
|
||||
data[3],
|
||||
data[4],
|
||||
data[5],
|
||||
data[6],
|
||||
data[7],
|
||||
data[8],
|
||||
instance->mac.data[0],
|
||||
instance->mac.data[1],
|
||||
instance->mac.data[2],
|
||||
instance->mac.data[3]);
|
||||
error = picopass_poller_write_block(
|
||||
instance, PICOPASS_SECURE_KD_BLOCK_INDEX, &new_block, &instance->mac);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_E(
|
||||
TAG, "Failed to write block %d. Error %d", PICOPASS_SECURE_KD_BLOCK_INDEX, error);
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
instance->state = PicopassPollerStateSuccess;
|
||||
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_success_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
instance->event.type = PicopassPollerEventTypeSuccess;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
furi_delay_ms(100);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_fail_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandReset;
|
||||
|
||||
instance->event.type = PicopassPollerEventTypeFail;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
picopass_poller_reset(instance);
|
||||
instance->state = PicopassPollerStateDetect;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static const PicopassPollerStateHandler picopass_poller_state_handler[PicopassPollerStateNum] = {
|
||||
[PicopassPollerStateRequestMode] = picopass_poller_request_mode_handler,
|
||||
[PicopassPollerStateDetect] = picopass_poller_detect_handler,
|
||||
[PicopassPollerStateSelect] = picopass_poller_select_handler,
|
||||
[PicopassPollerStatePreAuth] = picopass_poller_pre_auth_handler,
|
||||
[PicopassPollerStateCheckSecurity] = picopass_poller_check_security,
|
||||
[PicopassPollerStateAuth] = picopass_poller_auth_handler,
|
||||
[PicopassPollerStateReadBlock] = picopass_poller_read_block_handler,
|
||||
[PicopassPollerStateWriteBlock] = picopass_poller_write_block_handler,
|
||||
[PicopassPollerStateWriteKey] = picopass_poller_write_key_handler,
|
||||
[PicopassPollerStateParseCredential] = picopass_poller_parse_credential_handler,
|
||||
[PicopassPollerStateParseWiegand] = picopass_poller_parse_wiegand_handler,
|
||||
[PicopassPollerStateSuccess] = picopass_poller_success_handler,
|
||||
[PicopassPollerStateFail] = picopass_poller_fail_handler,
|
||||
};
|
||||
|
||||
static NfcCommand picopass_poller_callback(NfcEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
PicopassPoller* instance = context;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(event.type == NfcEventTypePollerReady) {
|
||||
command = picopass_poller_state_handler[instance->state](instance);
|
||||
}
|
||||
|
||||
if(instance->session_state == PicopassPollerSessionStateStopRequest) {
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void picopass_poller_start(
|
||||
PicopassPoller* instance,
|
||||
PicopassPollerCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->session_state == PicopassPollerSessionStateIdle);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
|
||||
instance->session_state = PicopassPollerSessionStateActive;
|
||||
nfc_start(instance->nfc, picopass_poller_callback, instance);
|
||||
}
|
||||
|
||||
void picopass_poller_stop(PicopassPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->session_state = PicopassPollerSessionStateStopRequest;
|
||||
nfc_stop(instance->nfc);
|
||||
instance->session_state = PicopassPollerSessionStateIdle;
|
||||
}
|
||||
|
||||
PicopassPoller* picopass_poller_alloc(Nfc* nfc) {
|
||||
furi_assert(nfc);
|
||||
|
||||
PicopassPoller* instance = malloc(sizeof(PicopassPoller));
|
||||
instance->nfc = nfc;
|
||||
nfc_config(instance->nfc, NfcModePoller, NfcTechIso15693);
|
||||
nfc_set_guard_time_us(instance->nfc, 10000);
|
||||
nfc_set_fdt_poll_fc(instance->nfc, 5000);
|
||||
nfc_set_fdt_poll_poll_us(instance->nfc, 1000);
|
||||
|
||||
instance->event.data = &instance->event_data;
|
||||
instance->data = malloc(sizeof(PicopassDeviceData));
|
||||
|
||||
instance->tx_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
|
||||
instance->tmp_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void picopass_poller_free(PicopassPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
free(instance->data);
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
bit_buffer_free(instance->tmp_buffer);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
const PicopassDeviceData* picopass_poller_get_data(PicopassPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
80
applications/external/picopass/protocol/picopass_poller.h
vendored
Normal file
80
applications/external/picopass/protocol/picopass_poller.h
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/nfc.h>
|
||||
#include "picopass_protocol.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
PicopassPollerEventTypeRequestMode,
|
||||
PicopassPollerEventTypeCardDetected,
|
||||
PicopassPollerEventTypeCardLost,
|
||||
PicopassPollerEventTypeRequestKey,
|
||||
PicopassPollerEventTypeRequestWriteBlock,
|
||||
PicopassPollerEventTypeRequestWriteKey,
|
||||
PicopassPollerEventTypeSuccess,
|
||||
PicopassPollerEventTypeFail,
|
||||
} PicopassPollerEventType;
|
||||
|
||||
typedef enum {
|
||||
PicopassPollerModeRead,
|
||||
PicopassPollerModeWrite,
|
||||
PicopassPollerModeWriteKey,
|
||||
} PicopassPollerMode;
|
||||
|
||||
typedef struct {
|
||||
PicopassPollerMode mode;
|
||||
} PicopassPollerEventDataRequestMode;
|
||||
|
||||
typedef struct {
|
||||
uint8_t key[PICOPASS_KEY_LEN];
|
||||
bool is_key_provided;
|
||||
bool is_elite_key;
|
||||
} PicopassPollerEventDataRequestKey;
|
||||
|
||||
typedef struct {
|
||||
bool perform_write;
|
||||
uint8_t block_num;
|
||||
const PicopassBlock* block;
|
||||
} PicopassPollerEventDataRequestWriteBlock;
|
||||
|
||||
typedef struct {
|
||||
const PicopassDeviceData* data;
|
||||
uint8_t key[PICOPASS_KEY_LEN];
|
||||
bool is_elite_key;
|
||||
} PicopassPollerEventDataRequestWriteKey;
|
||||
|
||||
typedef union {
|
||||
PicopassPollerEventDataRequestMode req_mode;
|
||||
PicopassPollerEventDataRequestKey req_key;
|
||||
PicopassPollerEventDataRequestWriteBlock req_write;
|
||||
PicopassPollerEventDataRequestWriteKey req_write_key;
|
||||
} PicopassPollerEventData;
|
||||
|
||||
typedef struct {
|
||||
PicopassPollerEventType type;
|
||||
PicopassPollerEventData* data;
|
||||
} PicopassPollerEvent;
|
||||
|
||||
typedef NfcCommand (*PicopassPollerCallback)(PicopassPollerEvent event, void* context);
|
||||
|
||||
typedef struct PicopassPoller PicopassPoller;
|
||||
|
||||
PicopassPoller* picopass_poller_alloc(Nfc* nfc);
|
||||
|
||||
void picopass_poller_free(PicopassPoller* instance);
|
||||
|
||||
void picopass_poller_start(
|
||||
PicopassPoller* instance,
|
||||
PicopassPollerCallback callback,
|
||||
void* context);
|
||||
|
||||
void picopass_poller_stop(PicopassPoller* instance);
|
||||
|
||||
const PicopassDeviceData* picopass_poller_get_data(PicopassPoller* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
217
applications/external/picopass/protocol/picopass_poller_i.c
vendored
Normal file
217
applications/external/picopass/protocol/picopass_poller_i.c
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "picopass_poller_i.h"
|
||||
|
||||
#include <nfc/helpers/iso14443_crc.h>
|
||||
|
||||
#define PICOPASS_POLLER_FWT_FC (100000)
|
||||
|
||||
#define TAG "Picopass"
|
||||
|
||||
static PicopassError picopass_poller_process_error(NfcError error) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
switch(error) {
|
||||
case NfcErrorNone:
|
||||
ret = PicopassErrorNone;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = PicopassErrorTimeout;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PicopassError picopass_poller_send_frame(
|
||||
PicopassPoller* instance,
|
||||
BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt_fc) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
NfcError error = nfc_poller_trx(instance->nfc, tx_buffer, rx_buffer, fwt_fc);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = picopass_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(!iso13239_crc_check(Iso13239CrcTypePicopass, rx_buffer)) {
|
||||
ret = PicopassErrorIncorrectCrc;
|
||||
break;
|
||||
}
|
||||
iso13239_crc_trim(instance->rx_buffer);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError picopass_poller_actall(PicopassPoller* instance) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_ACTALL);
|
||||
|
||||
NfcError error = nfc_poller_trx(
|
||||
instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(error != NfcErrorIncompleteFrame) {
|
||||
ret = picopass_poller_process_error(error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError picopass_poller_identify(
|
||||
PicopassPoller* instance,
|
||||
PicopassColResSerialNum* col_res_serial_num) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READ_OR_IDENTIFY);
|
||||
ret = picopass_poller_send_frame(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(ret != PicopassErrorNone) break;
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassColResSerialNum)) {
|
||||
ret = PicopassErrorProtocol;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(
|
||||
instance->rx_buffer, col_res_serial_num->data, sizeof(PicopassColResSerialNum));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError picopass_poller_select(
|
||||
PicopassPoller* instance,
|
||||
PicopassColResSerialNum* col_res_serial_num,
|
||||
PicopassSerialNum* serial_num) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_SELECT);
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, col_res_serial_num->data, sizeof(PicopassColResSerialNum));
|
||||
ret = picopass_poller_send_frame(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(ret != PicopassErrorNone) break;
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassSerialNum)) {
|
||||
ret = PicopassErrorProtocol;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(instance->rx_buffer, serial_num->data, sizeof(PicopassSerialNum));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError
|
||||
picopass_poller_read_block(PicopassPoller* instance, uint8_t block_num, PicopassBlock* block) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tmp_buffer);
|
||||
bit_buffer_append_byte(instance->tmp_buffer, block_num);
|
||||
iso13239_crc_append(Iso13239CrcTypePicopass, instance->tmp_buffer);
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READ_OR_IDENTIFY);
|
||||
bit_buffer_append(instance->tx_buffer, instance->tmp_buffer);
|
||||
|
||||
ret = picopass_poller_send_frame(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(ret != PicopassErrorNone) break;
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassBlock)) {
|
||||
ret = PicopassErrorProtocol;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(instance->rx_buffer, block->data, sizeof(PicopassBlock));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError
|
||||
picopass_poller_read_check(PicopassPoller* instance, PicopassReadCheckResp* read_check_resp) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READCHECK_KD);
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x02);
|
||||
|
||||
NfcError error = nfc_poller_trx(
|
||||
instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = picopass_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassReadCheckResp)) {
|
||||
ret = PicopassErrorProtocol;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(
|
||||
instance->rx_buffer, read_check_resp->data, sizeof(PicopassReadCheckResp));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError picopass_poller_check(
|
||||
PicopassPoller* instance,
|
||||
PicopassMac* mac,
|
||||
PicopassCheckResp* check_resp) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_CHECK);
|
||||
uint8_t null_arr[4] = {};
|
||||
bit_buffer_append_bytes(instance->tx_buffer, null_arr, sizeof(null_arr));
|
||||
bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
|
||||
|
||||
NfcError error = nfc_poller_trx(
|
||||
instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = picopass_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassCheckResp)) {
|
||||
ret = PicopassErrorProtocol;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(instance->rx_buffer, check_resp->data, sizeof(PicopassCheckResp));
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError picopass_poller_write_block(
|
||||
PicopassPoller* instance,
|
||||
uint8_t block_num,
|
||||
const PicopassBlock* block,
|
||||
const PicopassMac* mac) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_UPDATE);
|
||||
bit_buffer_append_byte(instance->tx_buffer, block_num);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, block->data, sizeof(PicopassBlock));
|
||||
bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
|
||||
|
||||
NfcError error = nfc_poller_trx(
|
||||
instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = picopass_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
85
applications/external/picopass/protocol/picopass_poller_i.h
vendored
Normal file
85
applications/external/picopass/protocol/picopass_poller_i.h
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include "picopass_poller.h"
|
||||
#include "picopass_protocol.h"
|
||||
|
||||
#include <nfc/helpers/iso13239_crc.h>
|
||||
|
||||
#define PICOPASS_POLLER_BUFFER_SIZE (255)
|
||||
#define PICOPASS_CRC_SIZE (2)
|
||||
|
||||
typedef enum {
|
||||
PicopassPollerSessionStateIdle,
|
||||
PicopassPollerSessionStateActive,
|
||||
PicopassPollerSessionStateStopRequest,
|
||||
} PicopassPollerSessionState;
|
||||
|
||||
typedef enum {
|
||||
PicopassPollerStateRequestMode,
|
||||
PicopassPollerStateDetect,
|
||||
PicopassPollerStateSelect,
|
||||
PicopassPollerStatePreAuth,
|
||||
PicopassPollerStateCheckSecurity,
|
||||
PicopassPollerStateAuth,
|
||||
PicopassPollerStateReadBlock,
|
||||
PicopassPollerStateWriteBlock,
|
||||
PicopassPollerStateWriteKey,
|
||||
PicopassPollerStateParseCredential,
|
||||
PicopassPollerStateParseWiegand,
|
||||
PicopassPollerStateSuccess,
|
||||
PicopassPollerStateFail,
|
||||
|
||||
PicopassPollerStateNum,
|
||||
} PicopassPollerState;
|
||||
|
||||
struct PicopassPoller {
|
||||
Nfc* nfc;
|
||||
PicopassPollerSessionState session_state;
|
||||
PicopassPollerState state;
|
||||
PicopassPollerMode mode;
|
||||
|
||||
PicopassColResSerialNum col_res_serial_num;
|
||||
PicopassSerialNum serial_num;
|
||||
PicopassMac mac;
|
||||
uint8_t div_key[8];
|
||||
uint8_t current_block;
|
||||
uint8_t app_limit;
|
||||
|
||||
PicopassDeviceData* data;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
BitBuffer* tmp_buffer;
|
||||
|
||||
PicopassPollerEvent event;
|
||||
PicopassPollerEventData event_data;
|
||||
PicopassPollerCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
PicopassError picopass_poller_actall(PicopassPoller* instance);
|
||||
|
||||
PicopassError
|
||||
picopass_poller_identify(PicopassPoller* instance, PicopassColResSerialNum* col_res_serial_num);
|
||||
|
||||
PicopassError picopass_poller_select(
|
||||
PicopassPoller* instance,
|
||||
PicopassColResSerialNum* col_res_serial_num,
|
||||
PicopassSerialNum* serial_num);
|
||||
|
||||
PicopassError
|
||||
picopass_poller_read_block(PicopassPoller* instance, uint8_t block_num, PicopassBlock* block);
|
||||
|
||||
PicopassError
|
||||
picopass_poller_read_check(PicopassPoller* instance, PicopassReadCheckResp* read_check_resp);
|
||||
|
||||
PicopassError picopass_poller_check(
|
||||
PicopassPoller* instance,
|
||||
PicopassMac* mac,
|
||||
PicopassCheckResp* check_resp);
|
||||
|
||||
PicopassError picopass_poller_write_block(
|
||||
PicopassPoller* instance,
|
||||
uint8_t block_num,
|
||||
const PicopassBlock* block,
|
||||
const PicopassMac* mac);
|
||||
48
applications/external/picopass/protocol/picopass_protocol.h
vendored
Normal file
48
applications/external/picopass/protocol/picopass_protocol.h
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "../picopass_device.h"
|
||||
|
||||
#define PICOPASS_BLOCK_LEN 8
|
||||
#define PICOPASS_MAX_APP_LIMIT 32
|
||||
#define PICOPASS_UID_LEN 8
|
||||
#define PICOPASS_READ_CHECK_RESP_LEN 8
|
||||
#define PICOPASS_CHECK_RESP_LEN 4
|
||||
#define PICOPASS_MAC_LEN 4
|
||||
#define PICOPASS_KEY_LEN 8
|
||||
|
||||
#define PICOPASS_FDT_LISTEN_FC (1000)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
PicopassErrorNone,
|
||||
PicopassErrorTimeout,
|
||||
PicopassErrorIncorrectCrc,
|
||||
PicopassErrorProtocol,
|
||||
} PicopassError;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[RFAL_PICOPASS_UID_LEN];
|
||||
} PicopassColResSerialNum;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[RFAL_PICOPASS_UID_LEN];
|
||||
} PicopassSerialNum;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[PICOPASS_READ_CHECK_RESP_LEN];
|
||||
} PicopassReadCheckResp;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[PICOPASS_CHECK_RESP_LEN];
|
||||
} PicopassCheckResp;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[PICOPASS_MAC_LEN];
|
||||
} PicopassMac;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
213
applications/external/picopass/rfal_picopass.c
vendored
213
applications/external/picopass/rfal_picopass.c
vendored
@@ -1,213 +0,0 @@
|
||||
#include "rfal_picopass.h"
|
||||
|
||||
#define RFAL_PICOPASS_TXRX_FLAGS \
|
||||
(FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \
|
||||
FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP)
|
||||
|
||||
#define TAG "RFAL_PICOPASS"
|
||||
|
||||
typedef struct {
|
||||
uint8_t CMD;
|
||||
uint8_t CSN[RFAL_PICOPASS_UID_LEN];
|
||||
} rfalPicoPassSelectReq;
|
||||
|
||||
typedef struct {
|
||||
uint8_t CMD;
|
||||
uint8_t null[4];
|
||||
uint8_t mac[4];
|
||||
} rfalPicoPassCheckReq;
|
||||
|
||||
static uint16_t rfalPicoPassUpdateCcitt(uint16_t crcSeed, uint8_t dataByte) {
|
||||
uint16_t crc = crcSeed;
|
||||
uint8_t dat = dataByte;
|
||||
|
||||
dat ^= (uint8_t)(crc & 0xFFU);
|
||||
dat ^= (dat << 4);
|
||||
|
||||
crc = (crc >> 8) ^ (((uint16_t)dat) << 8) ^ (((uint16_t)dat) << 3) ^ (((uint16_t)dat) >> 4);
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length) {
|
||||
uint16_t crc = preloadValue;
|
||||
uint16_t index;
|
||||
|
||||
for(index = 0; index < length; index++) {
|
||||
crc = rfalPicoPassUpdateCcitt(crc, buf[index]);
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
FuriHalNfcReturn rfalPicoPassPollerInitialize(void) {
|
||||
FuriHalNfcReturn ret;
|
||||
|
||||
ret = furi_hal_nfc_ll_set_mode(
|
||||
FuriHalNfcModePollPicopass, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48);
|
||||
if(ret != FuriHalNfcReturnOk) {
|
||||
return ret;
|
||||
};
|
||||
|
||||
furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc);
|
||||
furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_PICOPASS);
|
||||
furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER);
|
||||
furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER);
|
||||
|
||||
return FuriHalNfcReturnOk;
|
||||
}
|
||||
|
||||
FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void) {
|
||||
FuriHalNfcReturn ret;
|
||||
uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_ACTALL};
|
||||
uint8_t rxBuf[32] = {0};
|
||||
uint16_t recvLen = 0;
|
||||
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
|
||||
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
|
||||
|
||||
ret = furi_hal_nfc_ll_txrx(txBuf, 1, rxBuf, 32, &recvLen, flags, fwt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) {
|
||||
FuriHalNfcReturn ret;
|
||||
|
||||
uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_READ_OR_IDENTIFY};
|
||||
uint16_t recvLen = 0;
|
||||
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
|
||||
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
|
||||
|
||||
ret = furi_hal_nfc_ll_txrx(
|
||||
txBuf,
|
||||
sizeof(txBuf),
|
||||
(uint8_t*)idRes,
|
||||
sizeof(rfalPicoPassIdentifyRes),
|
||||
&recvLen,
|
||||
flags,
|
||||
fwt);
|
||||
// printf("identify rx: %d %s\n", recvLen, hex2Str(idRes->CSN, RFAL_PICOPASS_UID_LEN));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) {
|
||||
FuriHalNfcReturn ret;
|
||||
|
||||
rfalPicoPassSelectReq selReq;
|
||||
selReq.CMD = RFAL_PICOPASS_CMD_SELECT;
|
||||
memcpy(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN);
|
||||
uint16_t recvLen = 0;
|
||||
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
|
||||
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
|
||||
|
||||
ret = furi_hal_nfc_ll_txrx(
|
||||
(uint8_t*)&selReq,
|
||||
sizeof(rfalPicoPassSelectReq),
|
||||
(uint8_t*)selRes,
|
||||
sizeof(rfalPicoPassSelectRes),
|
||||
&recvLen,
|
||||
flags,
|
||||
fwt);
|
||||
// printf("select rx: %d %s\n", recvLen, hex2Str(selRes->CSN, RFAL_PICOPASS_UID_LEN));
|
||||
if(ret == FuriHalNfcReturnTimeout) {
|
||||
return FuriHalNfcReturnOk;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) {
|
||||
FuriHalNfcReturn ret;
|
||||
uint8_t txBuf[2] = {RFAL_PICOPASS_CMD_READCHECK_KD, 0x02};
|
||||
uint16_t recvLen = 0;
|
||||
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
|
||||
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
|
||||
|
||||
ret = furi_hal_nfc_ll_txrx(
|
||||
txBuf,
|
||||
sizeof(txBuf),
|
||||
(uint8_t*)rcRes,
|
||||
sizeof(rfalPicoPassReadCheckRes),
|
||||
&recvLen,
|
||||
flags,
|
||||
fwt);
|
||||
// printf("readcheck rx: %d %s\n", recvLen, hex2Str(rcRes->CCNR, 8));
|
||||
|
||||
if(ret == FuriHalNfcReturnCrc) {
|
||||
return FuriHalNfcReturnOk;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) {
|
||||
FuriHalNfcReturn ret;
|
||||
rfalPicoPassCheckReq chkReq;
|
||||
chkReq.CMD = RFAL_PICOPASS_CMD_CHECK;
|
||||
memcpy(chkReq.mac, mac, 4);
|
||||
memset(chkReq.null, 0, 4);
|
||||
uint16_t recvLen = 0;
|
||||
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
|
||||
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
|
||||
|
||||
// printf("check tx: %s\n", hex2Str((uint8_t *)&chkReq, sizeof(rfalPicoPassCheckReq)));
|
||||
ret = furi_hal_nfc_ll_txrx(
|
||||
(uint8_t*)&chkReq,
|
||||
sizeof(rfalPicoPassCheckReq),
|
||||
(uint8_t*)chkRes,
|
||||
sizeof(rfalPicoPassCheckRes),
|
||||
&recvLen,
|
||||
flags,
|
||||
fwt);
|
||||
// printf("check rx: %d %s\n", recvLen, hex2Str(chkRes->mac, 4));
|
||||
if(ret == FuriHalNfcReturnCrc) {
|
||||
return FuriHalNfcReturnOk;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes) {
|
||||
FuriHalNfcReturn ret;
|
||||
|
||||
uint8_t txBuf[4] = {RFAL_PICOPASS_CMD_READ_OR_IDENTIFY, 0, 0, 0};
|
||||
txBuf[1] = blockNum;
|
||||
uint16_t crc = rfalPicoPassCalculateCcitt(0xE012, txBuf + 1, 1);
|
||||
memcpy(txBuf + 2, &crc, sizeof(uint16_t));
|
||||
|
||||
uint16_t recvLen = 0;
|
||||
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
|
||||
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
|
||||
|
||||
ret = furi_hal_nfc_ll_txrx(
|
||||
txBuf,
|
||||
sizeof(txBuf),
|
||||
(uint8_t*)readRes,
|
||||
sizeof(rfalPicoPassReadBlockRes),
|
||||
&recvLen,
|
||||
flags,
|
||||
fwt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) {
|
||||
FuriHalNfcReturn ret;
|
||||
|
||||
uint8_t txBuf[14] = {RFAL_PICOPASS_CMD_UPDATE, blockNum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
memcpy(txBuf + 2, data, RFAL_PICOPASS_BLOCK_LEN);
|
||||
memcpy(txBuf + 10, mac, 4);
|
||||
|
||||
uint16_t recvLen = 0;
|
||||
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
|
||||
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
|
||||
rfalPicoPassReadBlockRes block;
|
||||
|
||||
ret = furi_hal_nfc_ll_txrx(
|
||||
txBuf, sizeof(txBuf), (uint8_t*)&block, sizeof(block), &recvLen, flags, fwt);
|
||||
|
||||
if(ret == FuriHalNfcReturnOk) {
|
||||
// TODO: compare response
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
15
applications/external/picopass/rfal_picopass.h
vendored
15
applications/external/picopass/rfal_picopass.h
vendored
@@ -3,7 +3,7 @@
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#define RFAL_PICOPASS_UID_LEN 8
|
||||
#define RFAL_PICOPASS_BLOCK_LEN 8
|
||||
#define PICOPASS_BLOCK_LEN 8
|
||||
|
||||
enum {
|
||||
// PicoPass command bytes:
|
||||
@@ -59,17 +59,6 @@ typedef struct {
|
||||
} rfalPicoPassCheckRes;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[RFAL_PICOPASS_BLOCK_LEN];
|
||||
uint8_t data[PICOPASS_BLOCK_LEN];
|
||||
uint8_t crc[2];
|
||||
} rfalPicoPassReadBlockRes;
|
||||
|
||||
uint16_t rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length);
|
||||
|
||||
FuriHalNfcReturn rfalPicoPassPollerInitialize(void);
|
||||
FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void);
|
||||
FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes);
|
||||
FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes);
|
||||
FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes);
|
||||
FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes);
|
||||
FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes);
|
||||
FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]);
|
||||
|
||||
@@ -20,14 +20,12 @@ void picopass_scene_card_menu_on_enter(void* context) {
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "Save", SubmenuIndexSave, picopass_scene_card_menu_submenu_callback, picopass);
|
||||
if(picopass->dev->dev_data.pacs.record.valid) {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Save as LF",
|
||||
SubmenuIndexSaveAsLF,
|
||||
picopass_scene_card_menu_submenu_callback,
|
||||
picopass);
|
||||
}
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Save as LFRFID",
|
||||
SubmenuIndexSaveAsLF,
|
||||
picopass_scene_card_menu_submenu_callback,
|
||||
picopass);
|
||||
submenu_add_item(
|
||||
submenu, "Write", SubmenuIndexWrite, picopass_scene_card_menu_submenu_callback, picopass);
|
||||
submenu_add_item(
|
||||
|
||||
@@ -25,32 +25,26 @@ void picopass_scene_device_info_on_enter(void* context) {
|
||||
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
|
||||
Widget* widget = picopass->widget;
|
||||
|
||||
uint8_t csn[RFAL_PICOPASS_BLOCK_LEN] = {0};
|
||||
memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);
|
||||
for(uint8_t i = 0; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
|
||||
uint8_t csn[PICOPASS_BLOCK_LEN] = {0};
|
||||
memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
|
||||
for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
furi_string_cat_printf(csn_str, "%02X ", csn[i]);
|
||||
}
|
||||
|
||||
if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) {
|
||||
if(pacs->bitLength == 0 || pacs->bitLength == 255) {
|
||||
// Neither of these are valid. Indicates the block was all 0x00 or all 0xff
|
||||
furi_string_cat_printf(wiegand_str, "Invalid PACS");
|
||||
} else {
|
||||
size_t bytesLength = pacs->record.bitLength / 8;
|
||||
if(pacs->record.bitLength % 8 > 0) {
|
||||
size_t bytesLength = pacs->bitLength / 8;
|
||||
if(pacs->bitLength % 8 > 0) {
|
||||
// Add extra byte if there are bits remaining
|
||||
bytesLength++;
|
||||
}
|
||||
furi_string_set(credential_str, "");
|
||||
for(uint8_t i = RFAL_PICOPASS_BLOCK_LEN - bytesLength; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
|
||||
for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
furi_string_cat_printf(credential_str, "%02X", pacs->credential[i]);
|
||||
}
|
||||
|
||||
if(pacs->record.valid) {
|
||||
furi_string_cat_printf(
|
||||
wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber);
|
||||
} else {
|
||||
furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength);
|
||||
}
|
||||
furi_string_cat_printf(wiegand_str, "%d bits", pacs->bitLength);
|
||||
|
||||
if(pacs->sio) {
|
||||
furi_string_cat_printf(credential_str, " +SIO");
|
||||
|
||||
@@ -1,172 +1,238 @@
|
||||
#include "../picopass_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
#include "../picopass_keys.h"
|
||||
|
||||
#define TAG "IclassEliteDictAttack"
|
||||
#define PICOPASS_SCENE_DICT_ATTACK_KEYS_BATCH_UPDATE (10)
|
||||
|
||||
typedef enum {
|
||||
DictAttackStateIdle,
|
||||
DictAttackStateUserDictInProgress,
|
||||
DictAttackStateFlipperDictInProgress,
|
||||
DictAttackStateStandardDictInProgress,
|
||||
} DictAttackState;
|
||||
enum {
|
||||
PicopassSceneEliteDictAttackDictEliteUser,
|
||||
PicopassSceneEliteDictAttackDictStandart,
|
||||
PicopassSceneEliteDictAttackDictElite,
|
||||
};
|
||||
|
||||
void picopass_dict_attack_worker_callback(PicopassWorkerEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
Picopass* picopass = context;
|
||||
view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
|
||||
const char* picopass_dict_name[] = {
|
||||
[PicopassSceneEliteDictAttackDictEliteUser] = "Elite User Dictionary",
|
||||
[PicopassSceneEliteDictAttackDictStandart] = "Standard System Dictionary",
|
||||
[PicopassSceneEliteDictAttackDictElite] = "Elite System Dictionary",
|
||||
};
|
||||
|
||||
static bool picopass_elite_dict_attack_change_dict(Picopass* picopass) {
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
uint32_t scene_state =
|
||||
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack);
|
||||
nfc_dict_free(picopass->dict);
|
||||
picopass->dict = NULL;
|
||||
if(scene_state == PicopassSceneEliteDictAttackDictElite) break;
|
||||
if(scene_state == PicopassSceneEliteDictAttackDictEliteUser) {
|
||||
if(!nfc_dict_check_presence(PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME)) break;
|
||||
picopass->dict = nfc_dict_alloc(
|
||||
PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME,
|
||||
NfcDictModeOpenExisting,
|
||||
PICOPASS_KEY_LEN);
|
||||
scene_state = PicopassSceneEliteDictAttackDictStandart;
|
||||
} else if(scene_state == PicopassSceneEliteDictAttackDictStandart) {
|
||||
if(!nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME)) break;
|
||||
picopass->dict = nfc_dict_alloc(
|
||||
PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME,
|
||||
NfcDictModeOpenExisting,
|
||||
PICOPASS_KEY_LEN);
|
||||
scene_state = PicopassSceneEliteDictAttackDictElite;
|
||||
}
|
||||
picopass->dict_attack_ctx.card_detected = true;
|
||||
picopass->dict_attack_ctx.total_keys = nfc_dict_get_total_keys(picopass->dict);
|
||||
picopass->dict_attack_ctx.current_key = 0;
|
||||
picopass->dict_attack_ctx.name = picopass_dict_name[scene_state];
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneEliteDictAttack, scene_state);
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void picopass_dict_attack_result_callback(void* context) {
|
||||
NfcCommand picopass_elite_dict_attack_worker_callback(PicopassPollerEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
Picopass* picopass = context;
|
||||
|
||||
if(event.type == PicopassPollerEventTypeRequestMode) {
|
||||
event.data->req_mode.mode = PicopassPollerModeRead;
|
||||
} else if(event.type == PicopassPollerEventTypeRequestKey) {
|
||||
uint8_t key[PICOPASS_KEY_LEN] = {};
|
||||
bool is_key_provided = true;
|
||||
if(!nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN)) {
|
||||
if(picopass_elite_dict_attack_change_dict(picopass)) {
|
||||
is_key_provided = nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN);
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
|
||||
} else {
|
||||
is_key_provided = false;
|
||||
}
|
||||
}
|
||||
uint32_t scene_state =
|
||||
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack);
|
||||
memcpy(event.data->req_key.key, key, PICOPASS_KEY_LEN);
|
||||
event.data->req_key.is_elite_key =
|
||||
(scene_state != PicopassSceneEliteDictAttackDictStandart);
|
||||
event.data->req_key.is_key_provided = is_key_provided;
|
||||
if(is_key_provided) {
|
||||
picopass->dict_attack_ctx.current_key++;
|
||||
if(picopass->dict_attack_ctx.current_key %
|
||||
PICOPASS_SCENE_DICT_ATTACK_KEYS_BATCH_UPDATE ==
|
||||
0) {
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
|
||||
}
|
||||
}
|
||||
} else if(event.type == PicopassPollerEventTypeSuccess) {
|
||||
const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
|
||||
memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
|
||||
} else if(event.type == PicopassPollerEventTypeFail) {
|
||||
const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
|
||||
memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
|
||||
} else if(event.type == PicopassPollerEventTypeCardLost) {
|
||||
picopass->dict_attack_ctx.card_detected = false;
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
|
||||
} else if(event.type == PicopassPollerEventTypeCardDetected) {
|
||||
picopass->dict_attack_ctx.card_detected = true;
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static void picopass_scene_elite_dict_attack_update_view(Picopass* instance) {
|
||||
if(instance->dict_attack_ctx.card_detected) {
|
||||
dict_attack_set_card_detected(instance->dict_attack);
|
||||
dict_attack_set_header(instance->dict_attack, instance->dict_attack_ctx.name);
|
||||
dict_attack_set_total_dict_keys(
|
||||
instance->dict_attack, instance->dict_attack_ctx.total_keys);
|
||||
dict_attack_set_current_dict_key(
|
||||
instance->dict_attack, instance->dict_attack_ctx.current_key);
|
||||
} else {
|
||||
dict_attack_set_card_removed(instance->dict_attack);
|
||||
}
|
||||
}
|
||||
|
||||
static void picopass_scene_elite_dict_attack_callback(void* context) {
|
||||
Picopass* instance = context;
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventDictAttackSkip);
|
||||
}
|
||||
|
||||
static void
|
||||
picopass_scene_elite_dict_attack_prepare_view(Picopass* picopass, DictAttackState state) {
|
||||
IclassEliteDictAttackData* dict_attack_data =
|
||||
&picopass->dev->dev_data.iclass_elite_dict_attack_data;
|
||||
PicopassWorkerState worker_state = PicopassWorkerStateReady;
|
||||
IclassEliteDict* dict = NULL;
|
||||
|
||||
// Identify scene state
|
||||
if(state == DictAttackStateIdle) {
|
||||
if(iclass_elite_dict_check_presence(IclassEliteDictTypeUser)) {
|
||||
FURI_LOG_D(TAG, "Starting with user dictionary");
|
||||
state = DictAttackStateUserDictInProgress;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Starting with standard dictionary");
|
||||
state = DictAttackStateStandardDictInProgress;
|
||||
}
|
||||
} else if(state == DictAttackStateUserDictInProgress) {
|
||||
FURI_LOG_D(TAG, "Moving from user dictionary to standard dictionary");
|
||||
state = DictAttackStateStandardDictInProgress;
|
||||
} else if(state == DictAttackStateStandardDictInProgress) {
|
||||
FURI_LOG_D(TAG, "Moving from standard dictionary to elite dictionary");
|
||||
state = DictAttackStateFlipperDictInProgress;
|
||||
}
|
||||
|
||||
// Setup view
|
||||
if(state == DictAttackStateUserDictInProgress) {
|
||||
worker_state = PicopassWorkerStateEliteDictAttack;
|
||||
dict_attack_set_header(picopass->dict_attack, "Elite User Dictionary");
|
||||
dict_attack_data->type = IclassEliteDictTypeUser;
|
||||
dict = iclass_elite_dict_alloc(IclassEliteDictTypeUser);
|
||||
|
||||
// If failed to load user dictionary - try the system dictionary
|
||||
if(!dict) {
|
||||
FURI_LOG_E(TAG, "User dictionary not found");
|
||||
state = DictAttackStateStandardDictInProgress;
|
||||
}
|
||||
}
|
||||
if(state == DictAttackStateStandardDictInProgress) {
|
||||
worker_state = PicopassWorkerStateEliteDictAttack;
|
||||
dict_attack_set_header(picopass->dict_attack, "Standard System Dictionary");
|
||||
dict_attack_data->type = IclassStandardDictTypeFlipper;
|
||||
dict = iclass_elite_dict_alloc(IclassStandardDictTypeFlipper);
|
||||
|
||||
if(!dict) {
|
||||
FURI_LOG_E(TAG, "Flipper standard dictionary not found");
|
||||
state = DictAttackStateFlipperDictInProgress;
|
||||
}
|
||||
}
|
||||
if(state == DictAttackStateFlipperDictInProgress) {
|
||||
worker_state = PicopassWorkerStateEliteDictAttack;
|
||||
dict_attack_set_header(picopass->dict_attack, "Elite System Dictionary");
|
||||
dict_attack_data->type = IclassEliteDictTypeFlipper;
|
||||
dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper);
|
||||
if(!dict) {
|
||||
FURI_LOG_E(TAG, "Flipper Elite dictionary not found");
|
||||
// Pass through to let the worker handle the failure
|
||||
}
|
||||
}
|
||||
// Free previous dictionary
|
||||
if(dict_attack_data->dict) {
|
||||
iclass_elite_dict_free(dict_attack_data->dict);
|
||||
}
|
||||
dict_attack_data->dict = dict;
|
||||
scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack, state);
|
||||
dict_attack_set_callback(
|
||||
picopass->dict_attack, picopass_dict_attack_result_callback, picopass);
|
||||
dict_attack_set_current_sector(picopass->dict_attack, 0);
|
||||
dict_attack_set_card_detected(picopass->dict_attack);
|
||||
dict_attack_set_total_dict_keys(
|
||||
picopass->dict_attack, dict ? iclass_elite_dict_get_total_keys(dict) : 0);
|
||||
picopass_worker_start(
|
||||
picopass->worker,
|
||||
worker_state,
|
||||
&picopass->dev->dev_data,
|
||||
picopass_dict_attack_worker_callback,
|
||||
picopass);
|
||||
instance->view_dispatcher, PicopassCustomEventDictAttackSkip);
|
||||
}
|
||||
|
||||
void picopass_scene_elite_dict_attack_on_enter(void* context) {
|
||||
Picopass* picopass = context;
|
||||
picopass_scene_elite_dict_attack_prepare_view(picopass, DictAttackStateIdle);
|
||||
dolphin_deed(DolphinDeedNfcRead);
|
||||
|
||||
// Setup dict attack context
|
||||
uint32_t state = PicopassSceneEliteDictAttackDictEliteUser;
|
||||
|
||||
bool use_user_dict = nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_USER_NAME);
|
||||
if(use_user_dict) {
|
||||
picopass->dict = nfc_dict_alloc(
|
||||
PICOPASS_ICLASS_ELITE_DICT_USER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
|
||||
if(nfc_dict_get_total_keys(picopass->dict) == 0) {
|
||||
nfc_dict_free(picopass->dict);
|
||||
use_user_dict = false;
|
||||
}
|
||||
}
|
||||
if(use_user_dict) {
|
||||
state = PicopassSceneEliteDictAttackDictEliteUser;
|
||||
} else {
|
||||
picopass->dict = nfc_dict_alloc(
|
||||
PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
|
||||
state = PicopassSceneEliteDictAttackDictStandart;
|
||||
}
|
||||
picopass->dict_attack_ctx.card_detected = true;
|
||||
picopass->dict_attack_ctx.total_keys = nfc_dict_get_total_keys(picopass->dict);
|
||||
picopass->dict_attack_ctx.current_key = 0;
|
||||
picopass->dict_attack_ctx.name = picopass_dict_name[state];
|
||||
scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack, state);
|
||||
|
||||
// Setup view
|
||||
picopass_scene_elite_dict_attack_update_view(picopass);
|
||||
dict_attack_set_callback(
|
||||
picopass->dict_attack, picopass_scene_elite_dict_attack_callback, picopass);
|
||||
|
||||
// Start worker
|
||||
picopass->poller = picopass_poller_alloc(picopass->nfc);
|
||||
picopass_poller_start(picopass->poller, picopass_elite_dict_attack_worker_callback, picopass);
|
||||
|
||||
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewDictAttack);
|
||||
picopass_blink_start(picopass);
|
||||
notification_message(picopass->notifications, &sequence_display_backlight_enforce_on);
|
||||
}
|
||||
|
||||
bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent event) {
|
||||
Picopass* picopass = context;
|
||||
bool consumed = false;
|
||||
|
||||
uint32_t state =
|
||||
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack);
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == PicopassWorkerEventSuccess) {
|
||||
if(state == DictAttackStateUserDictInProgress ||
|
||||
state == DictAttackStateStandardDictInProgress) {
|
||||
picopass_worker_stop(picopass->worker);
|
||||
picopass_scene_elite_dict_attack_prepare_view(picopass, state);
|
||||
consumed = true;
|
||||
if(event.event == PicopassCustomEventPollerSuccess) {
|
||||
if(memcmp(
|
||||
picopass->dev->dev_data.pacs.key,
|
||||
picopass_factory_debit_key,
|
||||
PICOPASS_BLOCK_LEN) == 0) {
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess);
|
||||
} else {
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.event == PicopassWorkerEventAborted) {
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
|
||||
consumed = true;
|
||||
} else if(event.event == PicopassWorkerEventCardDetected) {
|
||||
dict_attack_set_card_detected(picopass->dict_attack);
|
||||
consumed = true;
|
||||
} else if(event.event == PicopassWorkerEventNoCardDetected) {
|
||||
dict_attack_set_card_removed(picopass->dict_attack);
|
||||
consumed = true;
|
||||
} else if(event.event == PicopassWorkerEventNewDictKeyBatch) {
|
||||
dict_attack_inc_current_dict_key(picopass->dict_attack, PICOPASS_DICT_KEY_BATCH_SIZE);
|
||||
} else if(event.event == PicopassCustomEventDictAttackUpdateView) {
|
||||
picopass_scene_elite_dict_attack_update_view(picopass);
|
||||
consumed = true;
|
||||
} else if(event.event == PicopassCustomEventDictAttackSkip) {
|
||||
if(state == DictAttackStateUserDictInProgress) {
|
||||
picopass_worker_stop(picopass->worker);
|
||||
consumed = true;
|
||||
} else if(state == DictAttackStateFlipperDictInProgress) {
|
||||
picopass_worker_stop(picopass->worker);
|
||||
consumed = true;
|
||||
} else if(state == DictAttackStateStandardDictInProgress) {
|
||||
picopass_worker_stop(picopass->worker);
|
||||
consumed = true;
|
||||
uint32_t scene_state = scene_manager_get_scene_state(
|
||||
picopass->scene_manager, PicopassSceneEliteDictAttack);
|
||||
if(scene_state != PicopassSceneEliteDictAttackDictElite) {
|
||||
picopass_elite_dict_attack_change_dict(picopass);
|
||||
picopass_scene_elite_dict_attack_update_view(picopass);
|
||||
} else {
|
||||
if(memcmp(
|
||||
picopass->dev->dev_data.pacs.key,
|
||||
picopass_factory_debit_key,
|
||||
PICOPASS_BLOCK_LEN) == 0) {
|
||||
scene_manager_next_scene(
|
||||
picopass->scene_manager, PicopassSceneReadFactorySuccess);
|
||||
} else {
|
||||
scene_manager_next_scene(
|
||||
picopass->scene_manager, PicopassSceneReadCardSuccess);
|
||||
}
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = scene_manager_previous_scene(picopass->scene_manager);
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void picopass_scene_elite_dict_attack_on_exit(void* context) {
|
||||
Picopass* picopass = context;
|
||||
IclassEliteDictAttackData* dict_attack_data =
|
||||
&picopass->dev->dev_data.iclass_elite_dict_attack_data;
|
||||
// Stop worker
|
||||
picopass_worker_stop(picopass->worker);
|
||||
if(dict_attack_data->dict) {
|
||||
iclass_elite_dict_free(dict_attack_data->dict);
|
||||
dict_attack_data->dict = NULL;
|
||||
|
||||
if(picopass->dict) {
|
||||
nfc_dict_free(picopass->dict);
|
||||
picopass->dict = NULL;
|
||||
}
|
||||
dict_attack_reset(picopass->dict_attack);
|
||||
picopass->dict_attack_ctx.current_key = 0;
|
||||
picopass->dict_attack_ctx.total_keys = 0;
|
||||
|
||||
picopass_poller_stop(picopass->poller);
|
||||
picopass_poller_free(picopass->poller);
|
||||
|
||||
// Clear view
|
||||
popup_reset(picopass->popup);
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager,
|
||||
PicopassSceneEliteDictAttack,
|
||||
PicopassSceneEliteDictAttackDictEliteUser);
|
||||
|
||||
picopass_blink_stop(picopass);
|
||||
notification_message(picopass->notifications, &sequence_display_backlight_enforce_auto);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include "../picopass_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void picopass_emulate_worker_callback(PicopassWorkerEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
Picopass* picopass = context;
|
||||
view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
|
||||
NfcCommand picopass_scene_listener_callback(PicopassListenerEvent event, void* context) {
|
||||
UNUSED(event);
|
||||
UNUSED(context);
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
void picopass_scene_emulate_on_enter(void* context) {
|
||||
@@ -17,18 +18,11 @@ void picopass_scene_emulate_on_enter(void* context) {
|
||||
widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating");
|
||||
widget_add_string_element(widget, 89, 42, AlignCenter, AlignTop, FontPrimary, "PicoPass");
|
||||
|
||||
// Setup view
|
||||
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
|
||||
|
||||
// Start worker
|
||||
picopass_worker_start(
|
||||
picopass->worker,
|
||||
PicopassWorkerStateEmulate,
|
||||
&picopass->dev->dev_data,
|
||||
picopass_emulate_worker_callback,
|
||||
picopass);
|
||||
|
||||
picopass_blink_emulate_start(picopass);
|
||||
|
||||
picopass->listener = picopass_listener_alloc(picopass->nfc, &picopass->dev->dev_data);
|
||||
picopass_listener_start(picopass->listener, picopass_scene_listener_callback, picopass);
|
||||
}
|
||||
|
||||
bool picopass_scene_emulate_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -49,9 +43,8 @@ void picopass_scene_emulate_on_exit(void* context) {
|
||||
Picopass* picopass = context;
|
||||
|
||||
picopass_blink_stop(picopass);
|
||||
|
||||
// Stop worker
|
||||
picopass_worker_stop(picopass->worker);
|
||||
picopass_listener_stop(picopass->listener);
|
||||
picopass_listener_free(picopass->listener);
|
||||
|
||||
// Clear view
|
||||
widget_reset(picopass->widget);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "../picopass_i.h"
|
||||
#include <lib/toolbox/name_generator.h>
|
||||
#include <gui/modules/validators.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
void picopass_scene_key_input_text_input_callback(void* context) {
|
||||
Picopass* picopass = context;
|
||||
|
||||
picopass->dev->dev_data.pacs.elite_kdf = true;
|
||||
memcpy(picopass->dev->dev_data.pacs.key, picopass->byte_input_store, RFAL_PICOPASS_BLOCK_LEN);
|
||||
memcpy(picopass->write_key_context.key_to_write, picopass->byte_input_store, PICOPASS_KEY_LEN);
|
||||
picopass->write_key_context.is_elite = true;
|
||||
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventByteInputDone);
|
||||
}
|
||||
|
||||
@@ -21,7 +22,7 @@ void picopass_scene_key_input_on_enter(void* context) {
|
||||
NULL,
|
||||
picopass,
|
||||
picopass->byte_input_store,
|
||||
RFAL_PICOPASS_BLOCK_LEN);
|
||||
PICOPASS_BLOCK_LEN);
|
||||
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewByteInput);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,35 +65,43 @@ bool picopass_scene_key_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.event == SubmenuIndexWriteStandard) {
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteStandard);
|
||||
memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, RFAL_PICOPASS_BLOCK_LEN);
|
||||
picopass->dev->dev_data.pacs.elite_kdf = false;
|
||||
memcpy(
|
||||
picopass->write_key_context.key_to_write, picopass_iclass_key, PICOPASS_KEY_LEN);
|
||||
picopass->write_key_context.is_elite = false;
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWriteiCE) {
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE);
|
||||
memcpy(picopass->dev->dev_data.pacs.key, picopass_xice_key, RFAL_PICOPASS_BLOCK_LEN);
|
||||
picopass->dev->dev_data.pacs.elite_kdf = true;
|
||||
memcpy(picopass->write_key_context.key_to_write, picopass_xice_key, PICOPASS_KEY_LEN);
|
||||
picopass->write_key_context.is_elite = true;
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWriteiCL) {
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCL);
|
||||
memcpy(picopass->dev->dev_data.pacs.key, picopass_xicl_key, RFAL_PICOPASS_BLOCK_LEN);
|
||||
picopass->dev->dev_data.pacs.elite_kdf = false;
|
||||
memcpy(picopass->write_key_context.key_to_write, picopass_xicl_key, PICOPASS_KEY_LEN);
|
||||
picopass->write_key_context.is_elite = false;
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWriteiCS) {
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCS);
|
||||
memcpy(picopass->dev->dev_data.pacs.key, picopass_xics_key, RFAL_PICOPASS_BLOCK_LEN);
|
||||
picopass->dev->dev_data.pacs.elite_kdf = false;
|
||||
memcpy(picopass->write_key_context.key_to_write, picopass_xics_key, PICOPASS_KEY_LEN);
|
||||
picopass->write_key_context.is_elite = false;
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWriteCustom) {
|
||||
// If user dictionary, prepopulate with the first key
|
||||
if(iclass_elite_dict_check_presence(IclassEliteDictTypeUser)) {
|
||||
IclassEliteDict* dict = iclass_elite_dict_alloc(IclassEliteDictTypeUser);
|
||||
iclass_elite_dict_get_next_key(dict, picopass->byte_input_store);
|
||||
iclass_elite_dict_free(dict);
|
||||
}
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteCustom);
|
||||
// Key and elite_kdf = true are both set in key_input scene
|
||||
// Key and elite_kdf = true are both set in key_input scene after the value is input
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyInput);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
@@ -1,80 +1,103 @@
|
||||
#include "../picopass_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void picopass_loclass_worker_callback(PicopassWorkerEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
static NfcCommand
|
||||
picopass_scene_loclass_listener_callback(PicopassListenerEvent event, void* context) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
Picopass* picopass = context;
|
||||
view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
|
||||
|
||||
if(event.type == PicopassListenerEventTypeLoclassGotMac) {
|
||||
picopass->loclass_context.macs_collected++;
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventLoclassGotMac);
|
||||
} else if(event.type == PicopassListenerEventTypeLoclassGotStandardKey) {
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventLoclassGotStandardKey);
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void picopass_loclass_result_callback(void* context) {
|
||||
static void picopass_loclass_result_callback(void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
Picopass* picopass = context;
|
||||
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit);
|
||||
}
|
||||
|
||||
void picopass_scene_loclass_on_enter(void* context) {
|
||||
Picopass* picopass = context;
|
||||
|
||||
dolphin_deed(DolphinDeedNfcEmulate);
|
||||
|
||||
scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneLoclass, 0);
|
||||
|
||||
loclass_set_callback(picopass->loclass, picopass_loclass_result_callback, picopass);
|
||||
|
||||
// Start worker
|
||||
picopass_worker_start(
|
||||
picopass->worker,
|
||||
PicopassWorkerStateLoclass,
|
||||
&picopass->dev->dev_data,
|
||||
picopass_loclass_worker_callback,
|
||||
picopass);
|
||||
|
||||
picopass_blink_emulate_start(picopass);
|
||||
|
||||
loclass_set_header(picopass->loclass, "Loclass");
|
||||
|
||||
picopass_blink_emulate_start(picopass);
|
||||
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoclass);
|
||||
|
||||
PicopassDeviceData* data = malloc(sizeof(PicopassDeviceData));
|
||||
const uint8_t config_block[PICOPASS_BLOCK_LEN] = {
|
||||
0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
|
||||
memcpy(data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, config_block, sizeof(config_block));
|
||||
|
||||
const uint8_t epurse[PICOPASS_BLOCK_LEN] = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
memcpy(data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, epurse, sizeof(epurse));
|
||||
|
||||
const uint8_t aia[PICOPASS_BLOCK_LEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
memcpy(data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, aia, sizeof(aia));
|
||||
|
||||
picopass->listener = picopass_listener_alloc(picopass->nfc, data);
|
||||
free(data);
|
||||
if(picopass_listener_set_mode(picopass->listener, PicopassListenerModeLoclass)) {
|
||||
picopass_listener_start(
|
||||
picopass->listener, picopass_scene_loclass_listener_callback, picopass);
|
||||
} else {
|
||||
loclass_set_num_macs(picopass->loclass, 255);
|
||||
loclass_set_header(picopass->loclass, "Error Opening Log File");
|
||||
picopass_listener_free(picopass->listener);
|
||||
picopass->listener = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool picopass_scene_loclass_on_event(void* context, SceneManagerEvent event) {
|
||||
Picopass* picopass = context;
|
||||
bool consumed = false;
|
||||
|
||||
uint32_t loclass_macs_collected =
|
||||
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneLoclass);
|
||||
Picopass* picopass = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == PicopassWorkerEventLoclassGotMac) {
|
||||
loclass_macs_collected++;
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneLoclass, loclass_macs_collected);
|
||||
loclass_set_num_macs(picopass->loclass, loclass_macs_collected);
|
||||
if(loclass_macs_collected >= LOCLASS_MACS_TO_COLLECT) {
|
||||
if(event.event == PicopassCustomEventLoclassGotMac) {
|
||||
notification_message(picopass->notifications, &sequence_single_vibro);
|
||||
loclass_set_num_macs(picopass->loclass, picopass->loclass_context.macs_collected);
|
||||
if(picopass->loclass_context.macs_collected >= LOCLASS_MACS_TO_COLLECT) {
|
||||
notification_message(picopass->notifications, &sequence_double_vibro);
|
||||
scene_manager_previous_scene(picopass->scene_manager);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == PicopassWorkerEventLoclassGotStandardKey) {
|
||||
} else if(event.event == PicopassCustomEventLoclassGotStandardKey) {
|
||||
loclass_set_header(picopass->loclass, "Loclass (Got Std Key)");
|
||||
notification_message(picopass->notifications, &sequence_error);
|
||||
consumed = true;
|
||||
} else if(event.event == PicopassCustomEventViewExit) {
|
||||
consumed = scene_manager_previous_scene(picopass->scene_manager);
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = scene_manager_previous_scene(picopass->scene_manager);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void picopass_scene_loclass_on_exit(void* context) {
|
||||
Picopass* picopass = context;
|
||||
|
||||
if(picopass->listener) {
|
||||
picopass_listener_stop(picopass->listener);
|
||||
picopass_listener_free(picopass->listener);
|
||||
}
|
||||
picopass->loclass_context.macs_collected = 0;
|
||||
picopass_blink_stop(picopass);
|
||||
|
||||
// Stop worker
|
||||
picopass_worker_stop(picopass->worker);
|
||||
|
||||
loclass_reset(picopass->loclass);
|
||||
|
||||
// Clear view
|
||||
widget_reset(picopass->widget);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,68 @@
|
||||
#include <dolphin/dolphin.h>
|
||||
#include "../picopass_keys.h"
|
||||
|
||||
void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) {
|
||||
UNUSED(event);
|
||||
enum {
|
||||
PicopassSceneReadCardDictStandart,
|
||||
PicopassSceneReadCardDictElite,
|
||||
};
|
||||
|
||||
static bool picopass_read_card_change_dict(Picopass* picopass) {
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
uint32_t scene_state =
|
||||
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneReadCard);
|
||||
nfc_dict_free(picopass->dict);
|
||||
picopass->dict = NULL;
|
||||
if(scene_state == PicopassSceneReadCardDictElite) break;
|
||||
if(!nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME)) break;
|
||||
|
||||
picopass->dict = nfc_dict_alloc(
|
||||
PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictElite);
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
NfcCommand picopass_read_card_worker_callback(PicopassPollerEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
Picopass* picopass = context;
|
||||
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
|
||||
|
||||
if(event.type == PicopassPollerEventTypeRequestMode) {
|
||||
event.data->req_mode.mode = PicopassPollerModeRead;
|
||||
} else if(event.type == PicopassPollerEventTypeRequestKey) {
|
||||
uint8_t key[PICOPASS_KEY_LEN] = {};
|
||||
bool is_key_provided = true;
|
||||
if(!nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN)) {
|
||||
if(picopass_read_card_change_dict(picopass)) {
|
||||
is_key_provided = nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN);
|
||||
} else {
|
||||
is_key_provided = false;
|
||||
}
|
||||
}
|
||||
uint32_t scene_state =
|
||||
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneReadCard);
|
||||
memcpy(event.data->req_key.key, key, PICOPASS_KEY_LEN);
|
||||
event.data->req_key.is_elite_key = (scene_state == PicopassSceneReadCardDictElite);
|
||||
event.data->req_key.is_key_provided = is_key_provided;
|
||||
} else if(event.type == PicopassPollerEventTypeSuccess) {
|
||||
const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
|
||||
memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
|
||||
} else if(event.type == PicopassPollerEventTypeFail) {
|
||||
const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
|
||||
memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void picopass_scene_read_card_on_enter(void* context) {
|
||||
@@ -17,15 +75,15 @@ void picopass_scene_read_card_on_enter(void* context) {
|
||||
popup_set_header(popup, "Detecting\npicopass\ncard", 68, 30, AlignLeft, AlignTop);
|
||||
popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
|
||||
|
||||
picopass->dict = nfc_dict_alloc(
|
||||
PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictStandart);
|
||||
// Start worker
|
||||
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
|
||||
picopass_worker_start(
|
||||
picopass->worker,
|
||||
PicopassWorkerStateDetect,
|
||||
&picopass->dev->dev_data,
|
||||
picopass_read_card_worker_callback,
|
||||
picopass);
|
||||
picopass->poller = picopass_poller_alloc(picopass->nfc);
|
||||
picopass_poller_start(picopass->poller, picopass_read_card_worker_callback, picopass);
|
||||
|
||||
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
|
||||
picopass_blink_start(picopass);
|
||||
}
|
||||
|
||||
@@ -34,11 +92,11 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == PicopassCustomEventWorkerExit) {
|
||||
if(event.event == PicopassCustomEventPollerSuccess) {
|
||||
if(memcmp(
|
||||
picopass->dev->dev_data.pacs.key,
|
||||
picopass_factory_debit_key,
|
||||
RFAL_PICOPASS_BLOCK_LEN) == 0) {
|
||||
PICOPASS_BLOCK_LEN) == 0) {
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess);
|
||||
} else {
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
|
||||
@@ -52,10 +110,17 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
|
||||
void picopass_scene_read_card_on_exit(void* context) {
|
||||
Picopass* picopass = context;
|
||||
|
||||
// Stop worker
|
||||
picopass_worker_stop(picopass->worker);
|
||||
if(picopass->dict) {
|
||||
nfc_dict_free(picopass->dict);
|
||||
picopass->dict = NULL;
|
||||
}
|
||||
picopass_poller_stop(picopass->poller);
|
||||
picopass_poller_free(picopass->poller);
|
||||
|
||||
// Clear view
|
||||
popup_reset(picopass->popup);
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictStandart);
|
||||
|
||||
picopass_blink_stop(picopass);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "../picopass_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <picopass_keys.h>
|
||||
|
||||
void picopass_scene_read_card_success_widget_callback(
|
||||
GuiButtonType result,
|
||||
@@ -31,21 +32,26 @@ void picopass_scene_read_card_success_on_enter(void* context) {
|
||||
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
|
||||
Widget* widget = picopass->widget;
|
||||
|
||||
uint8_t csn[RFAL_PICOPASS_BLOCK_LEN] = {0};
|
||||
memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);
|
||||
for(uint8_t i = 0; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
|
||||
uint8_t csn[PICOPASS_BLOCK_LEN] = {0};
|
||||
memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
|
||||
for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
furi_string_cat_printf(csn_str, "%02X", csn[i]);
|
||||
}
|
||||
|
||||
bool no_key = picopass_is_memset(pacs->key, 0x00, RFAL_PICOPASS_BLOCK_LEN);
|
||||
// We can't test the pacs->key in case it is intentionally all 0's and we can't test the key block since it is populated with the diversified key before each key test, so we approximate with the PACS config block being blank.
|
||||
bool no_key = picopass_is_memset(
|
||||
AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0x00, PICOPASS_BLOCK_LEN);
|
||||
bool empty = picopass_is_memset(
|
||||
AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, RFAL_PICOPASS_BLOCK_LEN);
|
||||
AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN);
|
||||
|
||||
if(no_key) {
|
||||
furi_string_cat_printf(wiegand_str, "Read Failed");
|
||||
bool hid_csn = picopass_device_hid_csn(picopass->dev);
|
||||
|
||||
if(pacs->se_enabled) {
|
||||
furi_string_cat_printf(credential_str, "SE enabled");
|
||||
} else if(!hid_csn) {
|
||||
furi_string_cat_printf(credential_str, "Non-HID CSN");
|
||||
}
|
||||
|
||||
widget_add_button_element(
|
||||
@@ -62,7 +68,7 @@ void picopass_scene_read_card_success_on_enter(void* context) {
|
||||
"Menu",
|
||||
picopass_scene_read_card_success_widget_callback,
|
||||
picopass);
|
||||
} else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) {
|
||||
} else if(pacs->bitLength == 0 || pacs->bitLength == 255) {
|
||||
// Neither of these are valid. Indicates the block was all 0x00 or all 0xff
|
||||
furi_string_cat_printf(wiegand_str, "Invalid PACS");
|
||||
|
||||
@@ -76,18 +82,12 @@ void picopass_scene_read_card_success_on_enter(void* context) {
|
||||
picopass_scene_read_card_success_widget_callback,
|
||||
picopass);
|
||||
} else {
|
||||
size_t bytesLength = 1 + pacs->record.bitLength / 8;
|
||||
size_t bytesLength = 1 + pacs->bitLength / 8;
|
||||
furi_string_set(credential_str, "");
|
||||
for(uint8_t i = RFAL_PICOPASS_BLOCK_LEN - bytesLength; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
|
||||
for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
furi_string_cat_printf(credential_str, "%02X", pacs->credential[i]);
|
||||
}
|
||||
|
||||
if(pacs->record.valid) {
|
||||
furi_string_cat_printf(
|
||||
wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber);
|
||||
} else {
|
||||
furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength);
|
||||
}
|
||||
furi_string_cat_printf(wiegand_str, "%d bits", pacs->bitLength);
|
||||
|
||||
if(pacs->sio) {
|
||||
furi_string_cat_printf(credential_str, " +SIO");
|
||||
@@ -95,11 +95,24 @@ void picopass_scene_read_card_success_on_enter(void* context) {
|
||||
|
||||
if(pacs->key) {
|
||||
furi_string_cat_printf(key_str, "Key: ");
|
||||
uint8_t key[PICOPASS_BLOCK_LEN];
|
||||
memcpy(key, &pacs->key, PICOPASS_BLOCK_LEN);
|
||||
|
||||
uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
|
||||
memcpy(key, &pacs->key, RFAL_PICOPASS_BLOCK_LEN);
|
||||
for(uint8_t i = 0; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
|
||||
furi_string_cat_printf(key_str, "%02X", key[i]);
|
||||
bool standard_key = true;
|
||||
// Handle DES key being 56bits with parity in LSB
|
||||
for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
if((key[i] & 0xFE) != (picopass_iclass_key[i] & 0xFE)) {
|
||||
standard_key = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(standard_key) {
|
||||
furi_string_cat_printf(key_str, "Standard");
|
||||
} else {
|
||||
for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
furi_string_cat_printf(key_str, "%02X", key[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,9 @@ bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEve
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_previous_scene(picopass->scene_manager);
|
||||
} else if(event.event == GuiButtonTypeCenter) {
|
||||
memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, RFAL_PICOPASS_BLOCK_LEN);
|
||||
memcpy(
|
||||
picopass->write_key_context.key_to_write, picopass_iclass_key, PICOPASS_BLOCK_LEN);
|
||||
picopass->write_key_context.is_elite = false;
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../picopass_i.h"
|
||||
#include <toolbox/name_generator.h>
|
||||
#include <lib/toolbox/name_generator.h>
|
||||
#include <gui/modules/validators.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
@@ -16,7 +16,8 @@ void picopass_scene_save_name_on_enter(void* context) {
|
||||
TextInput* text_input = picopass->text_input;
|
||||
bool dev_name_empty = false;
|
||||
if(!strcmp(picopass->dev->dev_name, "")) {
|
||||
name_generator_make_auto(picopass->text_store, sizeof(picopass->text_store), "PicoPass");
|
||||
name_generator_make_auto(
|
||||
picopass->text_store, sizeof(picopass->text_store), PICOPASS_APP_FILE_PREFIX);
|
||||
dev_name_empty = true;
|
||||
} else {
|
||||
picopass_text_store_set(picopass, picopass->dev->dev_name);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "../picopass_i.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexInfo,
|
||||
SubmenuIndexWrite,
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexRename,
|
||||
SubmenuIndexDelete,
|
||||
};
|
||||
|
||||
void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
|
||||
@@ -1,10 +1,42 @@
|
||||
#include "../picopass_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
#include "../picopass_keys.h"
|
||||
|
||||
void picopass_write_card_worker_callback(PicopassWorkerEvent event, void* context) {
|
||||
UNUSED(event);
|
||||
#define PICOPASS_SCENE_WRITE_BLOCK_START 6
|
||||
#define PICOPASS_SCENE_WRITE_BLOCK_STOP 10
|
||||
|
||||
NfcCommand picopass_scene_write_poller_callback(PicopassPollerEvent event, void* context) {
|
||||
Picopass* picopass = context;
|
||||
view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(event.type == PicopassPollerEventTypeRequestMode) {
|
||||
event.data->req_mode.mode = PicopassPollerModeWrite;
|
||||
} else if(event.type == PicopassPollerEventTypeRequestKey) {
|
||||
memcpy(event.data->req_key.key, picopass_iclass_key, sizeof(picopass_iclass_key));
|
||||
event.data->req_key.is_elite_key = false;
|
||||
event.data->req_key.is_key_provided = true;
|
||||
} else if(event.type == PicopassPollerEventTypeRequestWriteBlock) {
|
||||
uint8_t block_num =
|
||||
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneWriteCard);
|
||||
if(block_num == PICOPASS_SCENE_WRITE_BLOCK_STOP) {
|
||||
event.data->req_write.perform_write = false;
|
||||
} else {
|
||||
event.data->req_write.block_num = block_num;
|
||||
event.data->req_write.block = &picopass->dev->dev_data.AA1[block_num];
|
||||
event.data->req_write.perform_write = true;
|
||||
block_num++;
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneWriteCard, block_num);
|
||||
}
|
||||
} else if(event.type == PicopassPollerEventTypeSuccess) {
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
|
||||
} else if(event.type == PicopassPollerEventTypeFail) {
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventPollerFail);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void picopass_scene_write_card_on_enter(void* context) {
|
||||
@@ -15,15 +47,14 @@ void picopass_scene_write_card_on_enter(void* context) {
|
||||
Popup* popup = picopass->popup;
|
||||
popup_set_header(popup, "Writing\npicopass\ncard", 68, 30, AlignLeft, AlignTop);
|
||||
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
|
||||
scene_manager_set_scene_state(
|
||||
picopass->scene_manager, PicopassSceneWriteCard, PICOPASS_SCENE_WRITE_BLOCK_START);
|
||||
|
||||
// Start worker
|
||||
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
|
||||
picopass_worker_start(
|
||||
picopass->worker,
|
||||
PicopassWorkerStateWrite,
|
||||
&picopass->dev->dev_data,
|
||||
picopass_write_card_worker_callback,
|
||||
picopass);
|
||||
|
||||
picopass->poller = picopass_poller_alloc(picopass->nfc);
|
||||
picopass_poller_start(picopass->poller, picopass_scene_write_poller_callback, picopass);
|
||||
|
||||
picopass_blink_start(picopass);
|
||||
}
|
||||
@@ -33,10 +64,10 @@ bool picopass_scene_write_card_on_event(void* context, SceneManagerEvent event)
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == PicopassWorkerEventFail) {
|
||||
if(event.event == PicopassCustomEventPollerFail) {
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardFailure);
|
||||
consumed = true;
|
||||
} else if(event.event == PicopassWorkerEventSuccess) {
|
||||
} else if(event.event == PicopassCustomEventPollerSuccess) {
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess);
|
||||
consumed = true;
|
||||
}
|
||||
@@ -48,7 +79,8 @@ void picopass_scene_write_card_on_exit(void* context) {
|
||||
Picopass* picopass = context;
|
||||
|
||||
// Stop worker
|
||||
picopass_worker_stop(picopass->worker);
|
||||
picopass_poller_stop(picopass->poller);
|
||||
picopass_poller_free(picopass->poller);
|
||||
// Clear view
|
||||
popup_reset(picopass->popup);
|
||||
|
||||
|
||||
@@ -1,10 +1,32 @@
|
||||
#include "../picopass_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) {
|
||||
UNUSED(event);
|
||||
NfcCommand picopass_scene_write_key_poller_callback(PicopassPollerEvent event, void* context) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
Picopass* picopass = context;
|
||||
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
|
||||
|
||||
if(event.type == PicopassPollerEventTypeRequestMode) {
|
||||
event.data->req_mode.mode = PicopassPollerModeWriteKey;
|
||||
} else if(event.type == PicopassPollerEventTypeRequestKey) {
|
||||
event.data->req_key.is_key_provided = true;
|
||||
memcpy(event.data->req_key.key, picopass->dev->dev_data.pacs.key, PICOPASS_KEY_LEN);
|
||||
event.data->req_key.is_elite_key = picopass->dev->dev_data.pacs.elite_kdf;
|
||||
} else if(event.type == PicopassPollerEventTypeRequestWriteKey) {
|
||||
event.data->req_write_key.data = &picopass->dev->dev_data;
|
||||
memcpy(
|
||||
event.data->req_write_key.key,
|
||||
picopass->write_key_context.key_to_write,
|
||||
PICOPASS_KEY_LEN);
|
||||
event.data->req_write_key.is_elite_key = picopass->write_key_context.is_elite;
|
||||
} else if(event.type == PicopassPollerEventTypeSuccess) {
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
|
||||
} else if(event.type == PicopassPollerEventTypeFail) {
|
||||
view_dispatcher_send_custom_event(
|
||||
picopass->view_dispatcher, PicopassCustomEventPollerFail);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void picopass_scene_write_key_on_enter(void* context) {
|
||||
@@ -18,14 +40,10 @@ void picopass_scene_write_key_on_enter(void* context) {
|
||||
|
||||
// Start worker
|
||||
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
|
||||
picopass_worker_start(
|
||||
picopass->worker,
|
||||
PicopassWorkerStateWriteKey,
|
||||
&picopass->dev->dev_data,
|
||||
picopass_write_key_worker_callback,
|
||||
picopass);
|
||||
|
||||
picopass_blink_start(picopass);
|
||||
|
||||
picopass->poller = picopass_poller_alloc(picopass->nfc);
|
||||
picopass_poller_start(picopass->poller, picopass_scene_write_key_poller_callback, picopass);
|
||||
}
|
||||
|
||||
bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -33,9 +51,12 @@ bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == PicopassCustomEventWorkerExit) {
|
||||
if(event.event == PicopassCustomEventPollerSuccess) {
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess);
|
||||
consumed = true;
|
||||
} else if(event.event == PicopassCustomEventPollerFail) {
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardFailure);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
@@ -45,7 +66,9 @@ void picopass_scene_write_key_on_exit(void* context) {
|
||||
Picopass* picopass = context;
|
||||
|
||||
// Stop worker
|
||||
picopass_worker_stop(picopass->worker);
|
||||
picopass_poller_stop(picopass->poller);
|
||||
picopass_poller_free(picopass->poller);
|
||||
|
||||
// Clear view
|
||||
popup_reset(picopass->popup);
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ struct DictAttack {
|
||||
|
||||
typedef struct {
|
||||
DictAttackState state;
|
||||
MfClassicType type;
|
||||
FuriString* header;
|
||||
uint8_t sectors_total;
|
||||
uint8_t sectors_read;
|
||||
@@ -123,7 +122,6 @@ void dict_attack_reset(DictAttack* dict_attack) {
|
||||
DictAttackViewModel * model,
|
||||
{
|
||||
model->state = DictAttackStateRead;
|
||||
model->type = MfClassicType1k;
|
||||
model->sectors_total = 1;
|
||||
model->sectors_read = 0;
|
||||
model->sector_current = 0;
|
||||
@@ -242,6 +240,15 @@ void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys
|
||||
true);
|
||||
}
|
||||
|
||||
void dict_attack_set_current_dict_key(DictAttack* dict_attack, uint16_t current_key) {
|
||||
furi_assert(dict_attack);
|
||||
with_view_model(
|
||||
dict_attack->view,
|
||||
DictAttackViewModel * model,
|
||||
{ model->dict_keys_current = current_key; },
|
||||
true);
|
||||
}
|
||||
|
||||
void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried) {
|
||||
furi_assert(dict_attack);
|
||||
with_view_model(
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include <gui/view.h>
|
||||
#include <gui/modules/widget.h>
|
||||
|
||||
#include <lib/nfc/protocols/mifare_classic.h>
|
||||
|
||||
typedef struct DictAttack DictAttack;
|
||||
|
||||
typedef void (*DictAttackCallback)(void* context);
|
||||
@@ -37,6 +35,8 @@ void dict_attack_inc_keys_found(DictAttack* dict_attack);
|
||||
|
||||
void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total);
|
||||
|
||||
void dict_attack_set_current_dict_key(DictAttack* dict_attack, uint16_t current_key);
|
||||
|
||||
void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried);
|
||||
|
||||
void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "loclass.h"
|
||||
#include "../picopass_worker_i.h"
|
||||
|
||||
#include <picopass_device.h>
|
||||
|
||||
#include <gui/elements.h>
|
||||
|
||||
@@ -21,6 +22,10 @@ static void loclass_draw_callback(Canvas* canvas, void* model) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header));
|
||||
|
||||
if(m->num_macs == 255) {
|
||||
return;
|
||||
}
|
||||
|
||||
float progress = m->num_macs == 0 ? 0 :
|
||||
(float)(m->num_macs) / (float)(LOCLASS_MACS_TO_COLLECT);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user