Explorar o código

picopass: Add support for non-secure cards (#106)

Tiernan %!s(int64=2) %!d(string=hai) anos
pai
achega
a06ad73b28

+ 3 - 1
.gitignore

@@ -1,4 +1,6 @@
 dist/*
 .vscode
 .clang-format
-.editorconfig
+.editorconfig
+.env
+.ufbt

+ 3 - 3
lib/loclass/optimized_cipher.c

@@ -214,7 +214,7 @@ void loclass_opt_doReaderMAC(uint8_t* cc_nr_p, uint8_t* div_key_p, uint8_t mac[4
 
 void loclass_opt_doReaderMAC_2(
     LoclassState_t _init,
-    uint8_t* nr,
+    const uint8_t* nr,
     uint8_t mac[4],
     const uint8_t* div_key_p) {
     loclass_opt_suc(div_key_p, &_init, nr, 4, false);
@@ -268,7 +268,7 @@ LoclassState_t loclass_opt_doTagMAC_1(uint8_t* cc_p, const uint8_t* div_key_p) {
  */
 void loclass_opt_doTagMAC_2(
     LoclassState_t _init,
-    uint8_t* nr,
+    const uint8_t* nr,
     uint8_t mac[4],
     const uint8_t* div_key_p) {
     loclass_opt_suc(div_key_p, &_init, nr, 4, true);
@@ -286,7 +286,7 @@ void loclass_opt_doTagMAC_2(
  */
 void loclass_opt_doBothMAC_2(
     LoclassState_t _init,
-    uint8_t* nr,
+    const uint8_t* nr,
     uint8_t rmac[4],
     uint8_t tmac[4],
     const uint8_t* div_key_p) {

+ 3 - 3
lib/loclass/optimized_cipher.h

@@ -60,7 +60,7 @@ void loclass_opt_doReaderMAC(uint8_t* cc_nr_p, uint8_t* div_key_p, uint8_t mac[4
 
 void loclass_opt_doReaderMAC_2(
     LoclassState_t _init,
-    uint8_t* nr,
+    const uint8_t* nr,
     uint8_t mac[4],
     const uint8_t* div_key_p);
 
@@ -89,7 +89,7 @@ LoclassState_t loclass_opt_doTagMAC_1(uint8_t* cc_p, const uint8_t* div_key_p);
  */
 void loclass_opt_doTagMAC_2(
     LoclassState_t _init,
-    uint8_t* nr,
+    const uint8_t* nr,
     uint8_t mac[4],
     const uint8_t* div_key_p);
 
@@ -103,7 +103,7 @@ void loclass_opt_doTagMAC_2(
  */
 void loclass_opt_doBothMAC_2(
     LoclassState_t _init,
-    uint8_t* nr,
+    const uint8_t* nr,
     uint8_t rmac[4],
     uint8_t tmac[4],
     const uint8_t* div_key_p);

+ 2 - 2
picopass_device.h

@@ -40,8 +40,8 @@
 // Crypt1 // 1+1 (crypt1+crypt0) means secured and keys changable
 #define PICOPASS_FUSE_CRYPT1 0x10
 // Crypt0 // 1+0 means secure and keys locked, 0+1 means not secured, 0+0 means disable auth entirely
-#define PICOPASS_FUSE_CRTPT0 0x08
-#define PICOPASS_FUSE_CRYPT10 (PICOPASS_FUSE_CRYPT1 | PICOPASS_FUSE_CRTPT0)
+#define PICOPASS_FUSE_CRYPT0 0x08
+#define PICOPASS_FUSE_CRYPT10 (PICOPASS_FUSE_CRYPT1 | PICOPASS_FUSE_CRYPT0)
 // Read Access, 1 meanns anonymous read enabled, 0 means must auth to read applicaion
 #define PICOPASS_FUSE_RA 0x01
 

+ 1 - 0
picopass_i.h

@@ -90,6 +90,7 @@ struct Picopass {
     PicopassPoller* poller;
     PicopassListener* listener;
     KeysDict* dict;
+    uint32_t last_error_notify_ticks;
 
     char text_store[PICOPASS_TEXT_STORE_SIZE];
     FuriString* text_box_store;

+ 42 - 21
protocol/picopass_listener.c

@@ -162,9 +162,15 @@ PicopassListenerCommand
         uint8_t block_num = bit_buffer_get_byte(buf, 1);
         if(block_num > PICOPASS_MAX_APP_LIMIT) break;
 
+        bool secured = (instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7] &
+                        PICOPASS_FUSE_CRYPT10) != PICOPASS_FUSE_CRYPT0;
+
+        // TODO: Check CRC?
+        // TODO: Check auth?
+
         bit_buffer_reset(instance->tx_buffer);
-        if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) ||
-           (block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
+        if(secured && ((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);
             }
@@ -193,6 +199,8 @@ static PicopassListenerCommand
         uint8_t block_num = bit_buffer_get_byte(buf, 1);
         if(block_num != PICOPASS_SECURE_EPURSE_BLOCK_INDEX) break;
 
+        // note that even non-secure chips seem to reply to READCHECK still
+
         // 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) &&
@@ -238,8 +246,7 @@ PicopassListenerCommand
 
         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));
+        const uint8_t* rx_data = bit_buffer_get_data(buf);
         loclass_opt_doReaderMAC_2(instance->cipher_state, &rx_data[1], rmac, key.data);
 
         if(!memcmp(&rx_data[5], rmac, 4)) {
@@ -300,7 +307,8 @@ PicopassListenerCommand
     return command;
 }
 
-PicopassListenerCommand picopass_listener_save_mac(PicopassListener* instance, uint8_t* rx_data) {
+PicopassListenerCommand
+    picopass_listener_save_mac(PicopassListener* instance, const uint8_t* rx_data) {
     PicopassListenerCommand command = PicopassListenerCommandSilent;
     Picopass* picopass = instance->context;
 
@@ -360,13 +368,15 @@ PicopassListenerCommand
     PicopassListenerCommand command = PicopassListenerCommandSilent;
 
     do {
+        bool secured = (instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7] &
+                        PICOPASS_FUSE_CRYPT10) != PICOPASS_FUSE_CRYPT0;
+        if(!secured) break;
+
         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));
         bool no_key = picopass_is_memset(key, 0x00, PICOPASS_BLOCK_LEN);
+        const uint8_t* rx_data = bit_buffer_get_data(buf);
 
         if(no_key) {
             // We're emulating a partial dump of an iClass SE card and should capture the NR and MAC
@@ -424,27 +434,31 @@ PicopassListenerCommand
 
         PicopassBlock config_block = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX];
         bool pers_mode = PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_PERS);
+        bool secured = (instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7] &
+                        PICOPASS_FUSE_CRYPT10) != PICOPASS_FUSE_CRYPT0;
 
         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))
+        if(!pers_mode && ((secured && block_num == PICOPASS_SECURE_AIA_BLOCK_INDEX) ||
+                          (!secured && block_num == PICOPASS_NONSECURE_AIA_BLOCK_INDEX)))
             break; // AIA can only be set in personalisation mode
-        if(!pers_mode &&
+        if(!pers_mode && secured &&
            ((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;
+            break; // TODO: Is this the right response?
 
         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;
+                break; // TODO: Is this the right response?
             }
         }
+
         // TODO: Check CRC/SIGN depending on if in secure mode
         // Check correct key
         // -> Kd only allows decrementing e-Purse
@@ -477,15 +491,17 @@ PicopassListenerCommand
             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);
+            if(secured) {
+                // ePurse updates swap first and second half of the block each update on secure cards
+                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) {
+            if(!pers_mode && secured) {
                 new_block = instance->data->AA1[block_num];
                 for(size_t i = 0; i < sizeof(PicopassBlock); i++) {
                     new_block.data[i] ^= rx_data[i + 2];
@@ -500,14 +516,14 @@ PicopassListenerCommand
         }
 
         instance->data->AA1[block_num] = new_block;
-        if((block_num == instance->key_block_num) ||
-           (block_num == PICOPASS_SECURE_EPURSE_BLOCK_INDEX)) {
+        if(secured && ((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)) {
+        if(secured && ((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);
@@ -539,12 +555,16 @@ PicopassListenerCommand
         uint8_t block_start = bit_buffer_get_byte(buf, 1);
         if(block_start + 4 >= PICOPASS_MAX_APP_LIMIT) break;
 
+        bool secured = (instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7] &
+                        PICOPASS_FUSE_CRYPT10) != PICOPASS_FUSE_CRYPT0;
+
         // 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)) {
+            if(secured &&
+               ((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);
                 }
@@ -622,6 +642,7 @@ static const PicopassListenerCmd picopass_listener_cmd_handlers[] = {
         .cmd_len_bits = 8 * 4,
         .handler = picopass_listener_read4_handler,
     },
+    // TODO: RFAL_PICOPASS_CMD_DETECT
 };
 
 PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data) {

+ 52 - 12
protocol/picopass_poller.c

@@ -64,11 +64,7 @@ NfcCommand picopass_poller_select_handler(PicopassPoller* instance) {
             break;
         }
 
-        if(instance->mode == PicopassPollerModeRead) {
-            instance->state = PicopassPollerStatePreAuth;
-        } else {
-            instance->state = PicopassPollerStateAuth;
-        }
+        instance->state = PicopassPollerStatePreAuth;
     } while(false);
 
     return command;
@@ -138,7 +134,7 @@ NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
             instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[6],
             instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[7]);
 
-        error = picopass_poller_read_block(instance, 5, &block);
+        error = picopass_poller_read_block(instance, PICOPASS_SECURE_AIA_BLOCK_INDEX, &block);
         if(error != PicopassErrorNone) {
             instance->state = PicopassPollerStateFail;
             break;
@@ -168,6 +164,28 @@ NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
 NfcCommand picopass_poller_check_security(PicopassPoller* instance) {
     NfcCommand command = NfcCommandContinue;
 
+    instance->secured = true;
+
+    uint8_t crypt =
+        (instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7] & PICOPASS_FUSE_CRYPT10);
+    switch(crypt) {
+    case 0:
+        FURI_LOG_D(TAG, "Secured page - Authentication disabled");
+        // Well this is awkward... We can try anyway though I guess...
+        break;
+    case PICOPASS_FUSE_CRYPT0:
+        FURI_LOG_D(TAG, "Non-secured page, skipping auth");
+        instance->secured = false;
+        picopass_poller_prepare_read(instance);
+        instance->state = PicopassPollerStateReadBlock;
+        return command;
+    case PICOPASS_FUSE_CRYPT0 | PICOPASS_FUSE_CRYPT1:
+        FURI_LOG_D(TAG, "Secured page - keys modifiable");
+        break;
+    case PICOPASS_FUSE_CRYPT1:
+        FURI_LOG_D(TAG, "Secured page - keys locked");
+    }
+
     // Thank you proxmark!
     PicopassBlock temp_block = {};
     memset(temp_block.data, 0xff, sizeof(PicopassBlock));
@@ -188,8 +206,14 @@ NfcCommand picopass_poller_check_security(PicopassPoller* instance) {
     if(instance->data->pacs.se_enabled) {
         FURI_LOG_D(TAG, "SE enabled");
     }
-    // Always try the NR-MAC auth in case we have the file.
-    instance->state = PicopassPollerStateNrMacAuth;
+
+    if(instance->mode == PicopassPollerModeRead) {
+        // Always try the NR-MAC auth in case we have the file.
+        instance->state = PicopassPollerStateNrMacAuth;
+    } else {
+        // NR-MAC auth doesn't allow for writing, so don't try
+        instance->state = PicopassPollerStateAuth;
+    }
     return command;
 }
 
@@ -222,7 +246,7 @@ NfcCommand picopass_poller_nr_mac_auth(PicopassPoller* instance) {
 
     // Set next state so breaking do/while will jump to it. If successful, do/while will set to ReadBlock
     if(instance->data->pacs.se_enabled) {
-        instance->state = PicopassPollerStateFail;
+        instance->state = PicopassPollerStateAuthFail;
     } else {
         // For non-SE, run through normal key check
         instance->state = PicopassPollerStateAuth;
@@ -319,7 +343,7 @@ NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) {
         if(command != NfcCommandContinue) break;
 
         if(!instance->event_data.req_key.is_key_provided) {
-            instance->state = PicopassPollerStateFail;
+            instance->state = PicopassPollerStateAuthFail;
             break;
         }
 
@@ -397,11 +421,15 @@ NfcCommand picopass_poller_read_block_handler(PicopassPoller* instance) {
 
     do {
         if(instance->current_block == instance->app_limit) {
-            instance->state = PicopassPollerStateParseCredential;
+            if(instance->secured) {
+                instance->state = PicopassPollerStateParseCredential;
+            } else {
+                instance->state = PicopassPollerStateSuccess;
+            }
             break;
         }
 
-        if(instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX) {
+        if(instance->secured && 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++;
         }
@@ -587,6 +615,17 @@ NfcCommand picopass_poller_fail_handler(PicopassPoller* instance) {
     return command;
 }
 
+NfcCommand picopass_poller_auth_fail_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandReset;
+
+    instance->event.type = PicopassPollerEventTypeAuthFail;
+    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,
@@ -602,6 +641,7 @@ static const PicopassPollerStateHandler picopass_poller_state_handler[PicopassPo
     [PicopassPollerStateParseWiegand] = picopass_poller_parse_wiegand_handler,
     [PicopassPollerStateSuccess] = picopass_poller_success_handler,
     [PicopassPollerStateFail] = picopass_poller_fail_handler,
+    [PicopassPollerStateAuthFail] = picopass_poller_auth_fail_handler,
 };
 
 static NfcCommand picopass_poller_callback(NfcEvent event, void* context) {

+ 1 - 0
protocol/picopass_poller.h

@@ -16,6 +16,7 @@ typedef enum {
     PicopassPollerEventTypeRequestWriteKey,
     PicopassPollerEventTypeSuccess,
     PicopassPollerEventTypeFail,
+    PicopassPollerEventTypeAuthFail,
 } PicopassPollerEventType;
 
 typedef enum {

+ 2 - 0
protocol/picopass_poller_i.h

@@ -29,6 +29,7 @@ typedef enum {
     PicopassPollerStateParseWiegand,
     PicopassPollerStateSuccess,
     PicopassPollerStateFail,
+    PicopassPollerStateAuthFail,
 
     PicopassPollerStateNum,
 } PicopassPollerState;
@@ -45,6 +46,7 @@ struct PicopassPoller {
     uint8_t div_key[8];
     uint8_t current_block;
     uint8_t app_limit;
+    bool secured;
 
     PicopassDeviceData* data;
 

+ 6 - 1
scenes/picopass_scene_card_menu.c

@@ -24,8 +24,13 @@ void picopass_scene_card_menu_on_enter(void* context) {
 
     bool sio = 0x30 == AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data[0];
     bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN);
+    bool secured = (AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7] & PICOPASS_FUSE_CRYPT10) !=
+                   PICOPASS_FUSE_CRYPT0;
 
-    if(no_key) {
+    if(!secured) {
+        submenu_add_item(
+            submenu, "Save", SubmenuIndexSave, picopass_scene_card_menu_submenu_callback, picopass);
+    } else if(no_key) {
         if(sio) {
             submenu_add_item(
                 submenu,

+ 4 - 6
scenes/picopass_scene_elite_dict_attack.c

@@ -87,12 +87,10 @@ NfcCommand picopass_elite_dict_attack_worker_callback(PicopassPollerEvent 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) {
+    } else if(
+        event.type == PicopassPollerEventTypeSuccess ||
+        event.type == PicopassPollerEventTypeFail ||
+        event.type == PicopassPollerEventTypeAuthFail) {
         const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
         memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
         view_dispatcher_send_custom_event(

+ 11 - 5
scenes/picopass_scene_read_card.c

@@ -51,16 +51,20 @@ NfcCommand picopass_read_card_worker_callback(PicopassPollerEvent event, void* c
         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) {
+    } else if(
+        event.type == PicopassPollerEventTypeSuccess ||
+        event.type == PicopassPollerEventTypeAuthFail) {
         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);
+        // the poller will retry, but provide some feedback to the user
+        uint32_t ticks = furi_get_tick();
+        if(picopass->last_error_notify_ticks + furi_ms_to_ticks(500) < ticks) {
+            picopass->last_error_notify_ticks = ticks;
+            notification_message(picopass->notifications, &sequence_error);
+        }
     }
 
     return command;
@@ -70,6 +74,8 @@ void picopass_scene_read_card_on_enter(void* context) {
     Picopass* picopass = context;
     dolphin_deed(DolphinDeedNfcRead);
 
+    picopass->last_error_notify_ticks = 0;
+
     // Setup view
     Popup* popup = picopass->popup;
     popup_set_header(popup, "Detecting\npicopass\ncard", 68, 30, AlignLeft, AlignTop);

+ 17 - 2
scenes/picopass_scene_read_card_success.c

@@ -45,10 +45,25 @@ void picopass_scene_read_card_success_on_enter(void* context) {
         AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN);
     bool SE = 0x30 == AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data[0];
     bool configCard = (AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data[7] >> 2 & 3) == 2;
+    bool secured = (AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7] & PICOPASS_FUSE_CRYPT10) !=
+                   PICOPASS_FUSE_CRYPT0;
+    bool hid_csn = picopass_device_hid_csn(picopass->dev);
 
-    if(no_key) {
+    if(!secured) {
+        furi_string_cat_printf(wiegand_str, "Non-Secured Chip");
+
+        if(!hid_csn) {
+            furi_string_cat_printf(credential_str, "Non-HID CSN");
+        }
+
+        widget_add_button_element(
+            widget,
+            GuiButtonTypeRight,
+            "More",
+            picopass_scene_read_card_success_widget_callback,
+            picopass);
+    } else 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");

+ 3 - 1
scenes/picopass_scene_write_card.c

@@ -31,7 +31,9 @@ NfcCommand picopass_scene_write_poller_callback(PicopassPollerEvent event, void*
     } else if(event.type == PicopassPollerEventTypeSuccess) {
         view_dispatcher_send_custom_event(
             picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
-    } else if(event.type == PicopassPollerEventTypeFail) {
+    } else if(
+        event.type == PicopassPollerEventTypeFail ||
+        event.type == PicopassPollerEventTypeAuthFail) {
         view_dispatcher_send_custom_event(
             picopass->view_dispatcher, PicopassCustomEventPollerFail);
     }

+ 3 - 1
scenes/picopass_scene_write_key.c

@@ -21,7 +21,9 @@ NfcCommand picopass_scene_write_key_poller_callback(PicopassPollerEvent event, v
     } else if(event.type == PicopassPollerEventTypeSuccess) {
         view_dispatcher_send_custom_event(
             picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
-    } else if(event.type == PicopassPollerEventTypeFail) {
+    } else if(
+        event.type == PicopassPollerEventTypeFail ||
+        event.type == PicopassPollerEventTypeAuthFail) {
         view_dispatcher_send_custom_event(
             picopass->view_dispatcher, PicopassCustomEventPollerFail);
     }