Picopass: factory key support, app rename and move to NFC category, minor code cleanup (#2417)
* message on successful card write * auth using factory key * auth using factory default * factory default screen * write standard iclass key * pass block explicitly * Fix array indexing, add empty detection * PicoPass: rename app and move to NFC group, minor code cleanup Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									eaf965c66f
								
							
						
					
					
						commit
						03f889962b
					
				| @ -1,6 +1,6 @@ | |||||||
| App( | App( | ||||||
|     appid="picopass", |     appid="picopass", | ||||||
|     name="PicoPass Reader", |     name="PicoPass", | ||||||
|     apptype=FlipperAppType.EXTERNAL, |     apptype=FlipperAppType.EXTERNAL, | ||||||
|     targets=["f7"], |     targets=["f7"], | ||||||
|     entry_point="picopass_app", |     entry_point="picopass_app", | ||||||
| @ -11,7 +11,7 @@ App( | |||||||
|     stack_size=4 * 1024, |     stack_size=4 * 1024, | ||||||
|     order=30, |     order=30, | ||||||
|     fap_icon="125_10px.png", |     fap_icon="125_10px.png", | ||||||
|     fap_category="Tools", |     fap_category="NFC", | ||||||
|     fap_libs=["mbedtls"], |     fap_libs=["mbedtls"], | ||||||
|     fap_private_libs=[ |     fap_private_libs=[ | ||||||
|         Lib( |         Lib( | ||||||
|  | |||||||
| @ -171,6 +171,16 @@ void picopass_show_loading_popup(void* context, bool show) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) { | ||||||
|  |     bool result = size > 0; | ||||||
|  |     while(size > 0) { | ||||||
|  |         result &= (*data == pattern); | ||||||
|  |         data++; | ||||||
|  |         size--; | ||||||
|  |     } | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| int32_t picopass_app(void* p) { | int32_t picopass_app(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
|     Picopass* picopass = picopass_alloc(); |     Picopass* picopass = picopass_alloc(); | ||||||
|  | |||||||
| @ -368,7 +368,7 @@ ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* r | |||||||
| 
 | 
 | ||||||
|         record->CardNumber = (bot >> 1) & 0xFFFF; |         record->CardNumber = (bot >> 1) & 0xFFFF; | ||||||
|         record->FacilityCode = (bot >> 17) & 0xFF; |         record->FacilityCode = (bot >> 17) & 0xFF; | ||||||
|         FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber); |         FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber); | ||||||
|         record->valid = true; |         record->valid = true; | ||||||
|     } else { |     } else { | ||||||
|         record->CardNumber = 0; |         record->CardNumber = 0; | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ | |||||||
| #define PICOPASS_KD_BLOCK_INDEX 3 | #define PICOPASS_KD_BLOCK_INDEX 3 | ||||||
| #define PICOPASS_KC_BLOCK_INDEX 4 | #define PICOPASS_KC_BLOCK_INDEX 4 | ||||||
| #define PICOPASS_AIA_BLOCK_INDEX 5 | #define PICOPASS_AIA_BLOCK_INDEX 5 | ||||||
|  | #define PICOPASS_PACS_CFG_BLOCK_INDEX 6 | ||||||
| 
 | 
 | ||||||
| #define PICOPASS_APP_FOLDER ANY_PATH("picopass") | #define PICOPASS_APP_FOLDER ANY_PATH("picopass") | ||||||
| #define PICOPASS_APP_EXTENSION ".picopass" | #define PICOPASS_APP_EXTENSION ".picopass" | ||||||
|  | |||||||
| @ -81,3 +81,15 @@ void picopass_blink_start(Picopass* picopass); | |||||||
| void picopass_blink_stop(Picopass* picopass); | void picopass_blink_stop(Picopass* picopass); | ||||||
| 
 | 
 | ||||||
| void picopass_show_loading_popup(void* context, bool show); | void picopass_show_loading_popup(void* context, bool show); | ||||||
|  | 
 | ||||||
|  | /** Check if memory is set to pattern
 | ||||||
|  |  * | ||||||
|  |  * @warning    zero size will return false | ||||||
|  |  * | ||||||
|  |  * @param[in]  data     Pointer to the byte array | ||||||
|  |  * @param[in]  pattern  The pattern | ||||||
|  |  * @param[in]  size     The byte array size | ||||||
|  |  * | ||||||
|  |  * @return     True if memory is set to pattern, false otherwise | ||||||
|  |  */ | ||||||
|  | bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size); | ||||||
|  | |||||||
| @ -5,7 +5,8 @@ | |||||||
| #define TAG "PicopassWorker" | #define TAG "PicopassWorker" | ||||||
| 
 | 
 | ||||||
| const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; | const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; | ||||||
| const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; | const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; | ||||||
|  | const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; | ||||||
| 
 | 
 | ||||||
| static void picopass_worker_enable_field() { | static void picopass_worker_enable_field() { | ||||||
|     furi_hal_nfc_ll_txrx_on(); |     furi_hal_nfc_ll_txrx_on(); | ||||||
| @ -197,6 +198,28 @@ static ReturnCode picopass_auth_standard(uint8_t* csn, uint8_t* div_key) { | |||||||
|     return rfalPicoPassPollerCheck(mac, &chkRes); |     return rfalPicoPassPollerCheck(mac, &chkRes); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static ReturnCode picopass_auth_factory(uint8_t* csn, uint8_t* div_key) { | ||||||
|  |     rfalPicoPassReadCheckRes rcRes; | ||||||
|  |     rfalPicoPassCheckRes chkRes; | ||||||
|  | 
 | ||||||
|  |     ReturnCode err; | ||||||
|  | 
 | ||||||
|  |     uint8_t mac[4] = {0}; | ||||||
|  |     uint8_t ccnr[12] = {0}; | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerReadCheck(&rcRes); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  |     memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
 | ||||||
|  | 
 | ||||||
|  |     loclass_diversifyKey(csn, picopass_factory_debit_key, div_key); | ||||||
|  |     loclass_opt_doReaderMAC(ccnr, div_key, mac); | ||||||
|  | 
 | ||||||
|  |     return rfalPicoPassPollerCheck(mac, &chkRes); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static ReturnCode picopass_auth_dict( | static ReturnCode picopass_auth_dict( | ||||||
|     uint8_t* csn, |     uint8_t* csn, | ||||||
|     PicopassPacs* pacs, |     PicopassPacs* pacs, | ||||||
| @ -264,14 +287,23 @@ static ReturnCode picopass_auth_dict( | |||||||
| ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { | ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { | ||||||
|     ReturnCode err; |     ReturnCode err; | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_E(TAG, "Trying standard legacy key"); |     FURI_LOG_I(TAG, "Trying standard legacy key"); | ||||||
|     err = picopass_auth_standard( |     err = picopass_auth_standard( | ||||||
|         AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); |         AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); | ||||||
|     if(err == ERR_NONE) { |     if(err == ERR_NONE) { | ||||||
|  |         memcpy(pacs->key, picopass_iclass_key, PICOPASS_BLOCK_LEN); | ||||||
|         return ERR_NONE; |         return ERR_NONE; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_E(TAG, "Starting user dictionary attack"); |     FURI_LOG_I(TAG, "Trying factory default key"); | ||||||
|  |     err = picopass_auth_factory( | ||||||
|  |         AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); | ||||||
|  |     if(err == ERR_NONE) { | ||||||
|  |         memcpy(pacs->key, picopass_factory_debit_key, PICOPASS_BLOCK_LEN); | ||||||
|  |         return ERR_NONE; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_I(TAG, "Starting user dictionary attack"); | ||||||
|     err = picopass_auth_dict( |     err = picopass_auth_dict( | ||||||
|         AA1[PICOPASS_CSN_BLOCK_INDEX].data, |         AA1[PICOPASS_CSN_BLOCK_INDEX].data, | ||||||
|         pacs, |         pacs, | ||||||
| @ -281,7 +313,7 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { | |||||||
|         return ERR_NONE; |         return ERR_NONE; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_E(TAG, "Starting in-built dictionary attack"); |     FURI_LOG_I(TAG, "Starting system dictionary attack"); | ||||||
|     err = picopass_auth_dict( |     err = picopass_auth_dict( | ||||||
|         AA1[PICOPASS_CSN_BLOCK_INDEX].data, |         AA1[PICOPASS_CSN_BLOCK_INDEX].data, | ||||||
|         pacs, |         pacs, | ||||||
| @ -406,6 +438,84 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) { | |||||||
|     return ERR_NONE; |     return ERR_NONE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* newBlock) { | ||||||
|  |     rfalPicoPassIdentifyRes idRes; | ||||||
|  |     rfalPicoPassSelectRes selRes; | ||||||
|  |     rfalPicoPassReadCheckRes rcRes; | ||||||
|  |     rfalPicoPassCheckRes chkRes; | ||||||
|  | 
 | ||||||
|  |     ReturnCode err; | ||||||
|  | 
 | ||||||
|  |     uint8_t div_key[8] = {0}; | ||||||
|  |     uint8_t mac[4] = {0}; | ||||||
|  |     uint8_t ccnr[12] = {0}; | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerIdentify(&idRes); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerReadCheck(&rcRes); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  |     memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
 | ||||||
|  | 
 | ||||||
|  |     loclass_diversifyKey(selRes.CSN, pacs->key, div_key); | ||||||
|  |     loclass_opt_doReaderMAC(ccnr, div_key, mac); | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerCheck(mac, &chkRes); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", blockNo); | ||||||
|  |     uint8_t data[9] = { | ||||||
|  |         blockNo, | ||||||
|  |         newBlock[0], | ||||||
|  |         newBlock[1], | ||||||
|  |         newBlock[2], | ||||||
|  |         newBlock[3], | ||||||
|  |         newBlock[4], | ||||||
|  |         newBlock[5], | ||||||
|  |         newBlock[6], | ||||||
|  |         newBlock[7]}; | ||||||
|  |     loclass_doMAC_N(data, sizeof(data), div_key, mac); | ||||||
|  |     FURI_LOG_D( | ||||||
|  |         TAG, | ||||||
|  |         "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x", | ||||||
|  |         blockNo, | ||||||
|  |         data[1], | ||||||
|  |         data[2], | ||||||
|  |         data[3], | ||||||
|  |         data[4], | ||||||
|  |         data[5], | ||||||
|  |         data[6], | ||||||
|  |         data[7], | ||||||
|  |         data[8], | ||||||
|  |         mac[0], | ||||||
|  |         mac[1], | ||||||
|  |         mac[2], | ||||||
|  |         mac[3]); | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerWriteBlock(data[0], data + 1, mac); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ERR_NONE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| int32_t picopass_worker_task(void* context) { | int32_t picopass_worker_task(void* context) { | ||||||
|     PicopassWorker* picopass_worker = context; |     PicopassWorker* picopass_worker = context; | ||||||
| 
 | 
 | ||||||
| @ -414,6 +524,8 @@ int32_t picopass_worker_task(void* context) { | |||||||
|         picopass_worker_detect(picopass_worker); |         picopass_worker_detect(picopass_worker); | ||||||
|     } else if(picopass_worker->state == PicopassWorkerStateWrite) { |     } else if(picopass_worker->state == PicopassWorkerStateWrite) { | ||||||
|         picopass_worker_write(picopass_worker); |         picopass_worker_write(picopass_worker); | ||||||
|  |     } else if(picopass_worker->state == PicopassWorkerStateWriteStandardKey) { | ||||||
|  |         picopass_worker_write_standard_key(picopass_worker); | ||||||
|     } |     } | ||||||
|     picopass_worker_disable_field(ERR_NONE); |     picopass_worker_disable_field(ERR_NONE); | ||||||
| 
 | 
 | ||||||
| @ -448,7 +560,7 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Thank you proxmark!
 |             // Thank you proxmark!
 | ||||||
|             pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); |             pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8); | ||||||
|             pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); |             pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); | ||||||
|             if(pacs->se_enabled) { |             if(pacs->se_enabled) { | ||||||
|                 FURI_LOG_D(TAG, "SE enabled"); |                 FURI_LOG_D(TAG, "SE enabled"); | ||||||
| @ -520,3 +632,46 @@ void picopass_worker_write(PicopassWorker* picopass_worker) { | |||||||
|         furi_delay_ms(100); |         furi_delay_ms(100); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) { | ||||||
|  |     PicopassDeviceData* dev_data = picopass_worker->dev_data; | ||||||
|  |     PicopassBlock* AA1 = dev_data->AA1; | ||||||
|  |     PicopassPacs* pacs = &dev_data->pacs; | ||||||
|  |     ReturnCode err; | ||||||
|  |     PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; | ||||||
|  | 
 | ||||||
|  |     uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; | ||||||
|  |     uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; | ||||||
|  |     uint8_t fuses = configBlock[7]; | ||||||
|  |     uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data; | ||||||
|  | 
 | ||||||
|  |     uint8_t newKey[PICOPASS_BLOCK_LEN] = {0}; | ||||||
|  |     loclass_diversifyKey(csn, picopass_iclass_key, newKey); | ||||||
|  | 
 | ||||||
|  |     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++) { | ||||||
|  |             newKey[i] ^= oldKey[i]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     while(picopass_worker->state == PicopassWorkerStateWriteStandardKey) { | ||||||
|  |         if(picopass_detect_card(1000) == ERR_NONE) { | ||||||
|  |             err = picopass_write_block(pacs, PICOPASS_KD_BLOCK_INDEX, newKey); | ||||||
|  |             if(err != ERR_NONE) { | ||||||
|  |                 FURI_LOG_E(TAG, "picopass_write_block error %d", err); | ||||||
|  |                 nextState = PicopassWorkerEventFail; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Notify caller and exit
 | ||||||
|  |             if(picopass_worker->callback) { | ||||||
|  |                 picopass_worker->callback(nextState, picopass_worker->context); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         furi_delay_ms(100); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ typedef enum { | |||||||
|     // Main worker states
 |     // Main worker states
 | ||||||
|     PicopassWorkerStateDetect, |     PicopassWorkerStateDetect, | ||||||
|     PicopassWorkerStateWrite, |     PicopassWorkerStateWrite, | ||||||
|  |     PicopassWorkerStateWriteStandardKey, | ||||||
|     // Transition
 |     // Transition
 | ||||||
|     PicopassWorkerStateStop, |     PicopassWorkerStateStop, | ||||||
| } PicopassWorkerState; | } PicopassWorkerState; | ||||||
|  | |||||||
| @ -31,3 +31,4 @@ int32_t picopass_worker_task(void* context); | |||||||
| 
 | 
 | ||||||
| void picopass_worker_detect(PicopassWorker* picopass_worker); | void picopass_worker_detect(PicopassWorker* picopass_worker); | ||||||
| void picopass_worker_write(PicopassWorker* picopass_worker); | void picopass_worker_write(PicopassWorker* picopass_worker); | ||||||
|  | void picopass_worker_write_standard_key(PicopassWorker* picopass_worker); | ||||||
|  | |||||||
| @ -11,3 +11,5 @@ ADD_SCENE(picopass, delete, Delete) | |||||||
| ADD_SCENE(picopass, delete_success, DeleteSuccess) | ADD_SCENE(picopass, delete_success, DeleteSuccess) | ||||||
| ADD_SCENE(picopass, write_card, WriteCard) | ADD_SCENE(picopass, write_card, WriteCard) | ||||||
| ADD_SCENE(picopass, write_card_success, WriteCardSuccess) | ADD_SCENE(picopass, write_card_success, WriteCardSuccess) | ||||||
|  | ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess) | ||||||
|  | ADD_SCENE(picopass, write_key, WriteKey) | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| #include "../picopass_i.h" | #include "../picopass_i.h" | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| 
 | 
 | ||||||
|  | const uint8_t picopass_factory_key_check[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; | ||||||
|  | 
 | ||||||
| void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { | void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { | ||||||
|     UNUSED(event); |     UNUSED(event); | ||||||
|     Picopass* picopass = context; |     Picopass* picopass = context; | ||||||
| @ -34,7 +36,14 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == PicopassCustomEventWorkerExit) { |         if(event.event == PicopassCustomEventWorkerExit) { | ||||||
|  |             if(memcmp( | ||||||
|  |                    picopass->dev->dev_data.pacs.key, | ||||||
|  |                    picopass_factory_key_check, | ||||||
|  |                    PICOPASS_BLOCK_LEN) == 0) { | ||||||
|  |                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess); | ||||||
|  |             } else { | ||||||
|                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); |                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); | ||||||
|  |             } | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ void picopass_scene_read_card_success_widget_callback( | |||||||
| 
 | 
 | ||||||
| void picopass_scene_read_card_success_on_enter(void* context) { | void picopass_scene_read_card_success_on_enter(void* context) { | ||||||
|     Picopass* picopass = context; |     Picopass* picopass = context; | ||||||
|  | 
 | ||||||
|     FuriString* csn_str = furi_string_alloc_set("CSN:"); |     FuriString* csn_str = furi_string_alloc_set("CSN:"); | ||||||
|     FuriString* credential_str = furi_string_alloc(); |     FuriString* credential_str = furi_string_alloc(); | ||||||
|     FuriString* wiegand_str = furi_string_alloc(); |     FuriString* wiegand_str = furi_string_alloc(); | ||||||
| @ -30,27 +31,31 @@ void picopass_scene_read_card_success_on_enter(void* context) { | |||||||
|     PicopassPacs* pacs = &picopass->dev->dev_data.pacs; |     PicopassPacs* pacs = &picopass->dev->dev_data.pacs; | ||||||
|     Widget* widget = picopass->widget; |     Widget* widget = picopass->widget; | ||||||
| 
 | 
 | ||||||
|     uint8_t csn[PICOPASS_BLOCK_LEN]; |     uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; | ||||||
|     memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN); |     memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); | ||||||
|     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { |     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { | ||||||
|         furi_string_cat_printf(csn_str, "%02X ", csn[i]); |         furi_string_cat_printf(csn_str, "%02X ", csn[i]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
 |     bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN); | ||||||
|     if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { |     bool empty = | ||||||
|  |         picopass_is_memset(AA1[PICOPASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN); | ||||||
|  | 
 | ||||||
|  |     if(no_key) { | ||||||
|         furi_string_cat_printf(wiegand_str, "Read Failed"); |         furi_string_cat_printf(wiegand_str, "Read Failed"); | ||||||
| 
 | 
 | ||||||
|         if(pacs->se_enabled) { |         if(pacs->se_enabled) { | ||||||
|             furi_string_cat_printf(credential_str, "SE enabled"); |             furi_string_cat_printf(credential_str, "SE enabled"); | ||||||
|         } |         } | ||||||
|  |     } else if(empty) { | ||||||
|  |         furi_string_cat_printf(wiegand_str, "Empty"); | ||||||
|  |     } else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { | ||||||
|  |         // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
 | ||||||
|  |         furi_string_cat_printf(wiegand_str, "Invalid PACS"); | ||||||
| 
 | 
 | ||||||
|         widget_add_button_element( |         if(pacs->se_enabled) { | ||||||
|             widget, |             furi_string_cat_printf(credential_str, "SE enabled"); | ||||||
|             GuiButtonTypeLeft, |         } | ||||||
|             "Retry", |  | ||||||
|             picopass_scene_read_card_success_widget_callback, |  | ||||||
|             picopass); |  | ||||||
| 
 |  | ||||||
|     } else { |     } else { | ||||||
|         size_t bytesLength = 1 + pacs->record.bitLength / 8; |         size_t bytesLength = 1 + pacs->record.bitLength / 8; | ||||||
|         furi_string_set(credential_str, ""); |         furi_string_set(credential_str, ""); | ||||||
| @ -82,13 +87,6 @@ void picopass_scene_read_card_success_on_enter(void* context) { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         widget_add_button_element( |  | ||||||
|             widget, |  | ||||||
|             GuiButtonTypeLeft, |  | ||||||
|             "Retry", |  | ||||||
|             picopass_scene_read_card_success_widget_callback, |  | ||||||
|             picopass); |  | ||||||
| 
 |  | ||||||
|         widget_add_button_element( |         widget_add_button_element( | ||||||
|             widget, |             widget, | ||||||
|             GuiButtonTypeRight, |             GuiButtonTypeRight, | ||||||
| @ -97,6 +95,13 @@ void picopass_scene_read_card_success_on_enter(void* context) { | |||||||
|             picopass); |             picopass); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, | ||||||
|  |         GuiButtonTypeLeft, | ||||||
|  |         "Retry", | ||||||
|  |         picopass_scene_read_card_success_widget_callback, | ||||||
|  |         picopass); | ||||||
|  | 
 | ||||||
|     widget_add_string_element( |     widget_add_string_element( | ||||||
|         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); |         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); | ||||||
|     widget_add_string_element( |     widget_add_string_element( | ||||||
|  | |||||||
| @ -0,0 +1,78 @@ | |||||||
|  | #include "../picopass_i.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | void picopass_scene_read_factory_success_widget_callback( | ||||||
|  |     GuiButtonType result, | ||||||
|  |     InputType type, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Picopass* picopass = context; | ||||||
|  | 
 | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(picopass->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void picopass_scene_read_factory_success_on_enter(void* context) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  |     FuriString* title = furi_string_alloc_set("Factory Default"); | ||||||
|  |     FuriString* subtitle = furi_string_alloc_set(""); | ||||||
|  | 
 | ||||||
|  |     DOLPHIN_DEED(DolphinDeedNfcReadSuccess); | ||||||
|  | 
 | ||||||
|  |     // Send notification
 | ||||||
|  |     notification_message(picopass->notifications, &sequence_success); | ||||||
|  | 
 | ||||||
|  |     // Setup view
 | ||||||
|  |     Widget* widget = picopass->widget; | ||||||
|  |     //PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
 | ||||||
|  |     PicopassBlock* AA1 = picopass->dev->dev_data.AA1; | ||||||
|  | 
 | ||||||
|  |     uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; | ||||||
|  |     uint8_t fuses = configBlock[7]; | ||||||
|  | 
 | ||||||
|  |     if((fuses & 0x80) == 0x80) { | ||||||
|  |         furi_string_cat_printf(subtitle, "Personalization mode"); | ||||||
|  |     } else { | ||||||
|  |         furi_string_cat_printf(subtitle, "Application mode"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, | ||||||
|  |         GuiButtonTypeCenter, | ||||||
|  |         "Write Standard iClass Key", | ||||||
|  |         picopass_scene_read_factory_success_widget_callback, | ||||||
|  |         picopass); | ||||||
|  | 
 | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(title)); | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(subtitle)); | ||||||
|  | 
 | ||||||
|  |     furi_string_free(title); | ||||||
|  |     furi_string_free(subtitle); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_previous_scene(picopass->scene_manager); | ||||||
|  |         } else if(event.event == GuiButtonTypeCenter) { | ||||||
|  |             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void picopass_scene_read_factory_success_on_exit(void* context) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  | 
 | ||||||
|  |     // Clear view
 | ||||||
|  |     widget_reset(picopass->widget); | ||||||
|  | } | ||||||
| @ -16,6 +16,7 @@ void picopass_scene_write_card_success_widget_callback( | |||||||
| void picopass_scene_write_card_success_on_enter(void* context) { | void picopass_scene_write_card_success_on_enter(void* context) { | ||||||
|     Picopass* picopass = context; |     Picopass* picopass = context; | ||||||
|     Widget* widget = picopass->widget; |     Widget* widget = picopass->widget; | ||||||
|  |     FuriString* str = furi_string_alloc_set("Write Success!"); | ||||||
| 
 | 
 | ||||||
|     DOLPHIN_DEED(DolphinDeedNfcReadSuccess); |     DOLPHIN_DEED(DolphinDeedNfcReadSuccess); | ||||||
| 
 | 
 | ||||||
| @ -29,6 +30,18 @@ void picopass_scene_write_card_success_on_enter(void* context) { | |||||||
|         picopass_scene_write_card_success_widget_callback, |         picopass_scene_write_card_success_widget_callback, | ||||||
|         picopass); |         picopass); | ||||||
| 
 | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, | ||||||
|  |         GuiButtonTypeRight, | ||||||
|  |         "Menu", | ||||||
|  |         picopass_scene_write_card_success_widget_callback, | ||||||
|  |         picopass); | ||||||
|  | 
 | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(str)); | ||||||
|  | 
 | ||||||
|  |     furi_string_free(str); | ||||||
|  | 
 | ||||||
|     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); |     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -0,0 +1,53 @@ | |||||||
|  | #include "../picopass_i.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) { | ||||||
|  |     UNUSED(event); | ||||||
|  |     Picopass* picopass = context; | ||||||
|  |     view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void picopass_scene_write_key_on_enter(void* context) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  |     DOLPHIN_DEED(DolphinDeedNfcSave); | ||||||
|  | 
 | ||||||
|  |     // Setup view
 | ||||||
|  |     Popup* popup = picopass->popup; | ||||||
|  |     popup_set_header(popup, "Writing\niClass\nkey", 68, 30, AlignLeft, AlignTop); | ||||||
|  |     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); | ||||||
|  | 
 | ||||||
|  |     // Start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); | ||||||
|  |     picopass_worker_start( | ||||||
|  |         picopass->worker, | ||||||
|  |         PicopassWorkerStateWriteStandardKey, | ||||||
|  |         &picopass->dev->dev_data, | ||||||
|  |         picopass_write_key_worker_callback, | ||||||
|  |         picopass); | ||||||
|  | 
 | ||||||
|  |     picopass_blink_start(picopass); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == PicopassCustomEventWorkerExit) { | ||||||
|  |             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void picopass_scene_write_key_on_exit(void* context) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  | 
 | ||||||
|  |     // Stop worker
 | ||||||
|  |     picopass_worker_stop(picopass->worker); | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(picopass->popup); | ||||||
|  | 
 | ||||||
|  |     picopass_blink_stop(picopass); | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Eric Betts
						Eric Betts