Sfoglia il codice sorgente

Improve loclass logic for readers doing keyrolling. (#50)

Tiernan 2 anni fa
parent
commit
d9dec5b20e

+ 5 - 1
lib/loclass/optimized_cipher.c

@@ -296,7 +296,11 @@ void loclass_opt_doBothMAC_2(
     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};

+ 5 - 1
lib/loclass/optimized_cipher.h

@@ -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

+ 1 - 1
lib/loclass/optimized_ikeys.c

@@ -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

+ 1 - 1
lib/loclass/optimized_ikeys.h

@@ -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

+ 1 - 1
loclass_writer.c

@@ -99,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;
-}
+}

+ 9 - 0
picopass_device.h

@@ -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
@@ -70,6 +77,7 @@ typedef enum {
     PicopassEmulatorStateIdle,
     PicopassEmulatorStateActive,
     PicopassEmulatorStateSelected,
+    PicopassEmulatorStateStopEmulation,
 } PicopassEmulatorState;
 
 typedef struct {
@@ -110,6 +118,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;
 

+ 0 - 4
picopass_i.h

@@ -31,10 +31,6 @@
 
 #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)
-
 enum PicopassCustomEvent {
     // Reserve first 100 events for button types and indexes, starting from 0
     PicopassCustomEventReserved = 100,

+ 69 - 31
picopass_worker.c

@@ -835,22 +835,37 @@ static inline void picopass_emu_write_blocks(
         block_count * RFAL_PICOPASS_BLOCK_LEN);
 }
 
-static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
+static void picopass_init_cipher_state_key(
+    NfcVData* nfcv_data,
+    PicopassEmulatorCtx* ctx,
+    const uint8_t key[RFAL_PICOPASS_BLOCK_LEN]) {
     uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
+    picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
+
+    ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
+}
+
+static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
     uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
 
-    picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
     picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1);
 
-    ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
+    picopass_init_cipher_state_key(nfcv_data, ctx, key);
 }
 
 static void
     loclass_update_csn(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
-    // collect two nonces in a row for each CSN
-    uint8_t csn_num = (ctx->key_block_num / 2) % LOCLASS_NUM_CSNS;
-    memcpy(nfc_data->uid, loclass_csns[csn_num], RFAL_PICOPASS_BLOCK_LEN);
-    picopass_emu_write_blocks(nfcv_data, loclass_csns[csn_num], PICOPASS_CSN_BLOCK_INDEX, 1);
+    // collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
+    const uint8_t* csn =
+        loclass_csns[(ctx->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
+    memcpy(nfc_data->uid, csn, RFAL_PICOPASS_BLOCK_LEN);
+    picopass_emu_write_blocks(nfcv_data, csn, PICOPASS_CSN_BLOCK_INDEX, 1);
+
+    uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
+    loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
+    picopass_emu_write_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);
+
+    picopass_init_cipher_state_key(nfcv_data, ctx, key);
 }
 
 static void picopass_emu_handle_packet(
@@ -865,7 +880,7 @@ static void picopass_emu_handle_packet(
 
     const uint8_t block_ff[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
 
-    if(nfcv_data->frame_length < 1) {
+    if(nfcv_data->frame_length < 1 || ctx->state == PicopassEmulatorStateStopEmulation) {
         return;
     }
 
@@ -999,6 +1014,8 @@ static void picopass_emu_handle_packet(
             return;
         }
 
+        // 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(ctx->key_block_num != key_block_num && !ctx->loclass_mode) {
             ctx->key_block_num = key_block_num;
             picopass_init_cipher_state(nfcv_data, ctx);
@@ -1020,13 +1037,13 @@ static void picopass_emu_handle_packet(
             uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
             picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
 
-            // Check if the nonce is from a standard key
+#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY
             uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
-            loclass_iclass_calc_div_key(nfc_data->uid, picopass_iclass_key, key, false);
-            ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
+            // loclass mode stores the derived standard debit key in Kd to check
+            picopass_emu_read_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);
 
             uint8_t rmac[4];
-            loclass_opt_doBothMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, response, key);
+            loclass_opt_doReaderMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, key);
 
             if(!memcmp(nfcv_data->frame + 5, rmac, 4)) {
                 // MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
@@ -1035,25 +1052,46 @@ static void picopass_emu_handle_packet(
                 FURI_LOG_W(TAG, "loclass: standard key detected during collection");
                 ctx->loclass_got_std_key = true;
 
-                ctx->state = PicopassEmulatorStateIdle;
+                // 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
+
                 return;
             }
+#endif
 
-            // Copy CHALLENGE (nr) and READERSIGNATURE (mac) from frame
-            uint8_t nr[4];
-            memcpy(nr, nfcv_data->frame + 1, 4);
-            uint8_t mac[4];
-            memcpy(mac, nfcv_data->frame + 5, 4);
-
-            FURI_LOG_I(TAG, "loclass: got nr/mac pair");
-            loclass_writer_write_params(
-                ctx->loclass_writer, ctx->key_block_num, nfc_data->uid, cc, nr, mac);
-
-            // Rotate to the next CSN
-            ctx->key_block_num = (ctx->key_block_num + 1) % (LOCLASS_NUM_CSNS * 2);
-            loclass_update_csn(nfc_data, nfcv_data, ctx);
+            // Save to buffer to defer flushing when we rotate CSN
+            memcpy(
+                ctx->loclass_mac_buffer + ((ctx->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
+                nfcv_data->frame + 1,
+                8);
+
+            // Rotate to the next CSN/attempt
+            ctx->key_block_num++;
+
+            // CSN changed
+            if(ctx->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
+                // Flush NR-MACs for this CSN to SD card
+                uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
+                picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
+
+                for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
+                    loclass_writer_write_params(
+                        ctx->loclass_writer,
+                        ctx->key_block_num + i - LOCLASS_NUM_PER_CSN,
+                        nfc_data->uid,
+                        cc,
+                        ctx->loclass_mac_buffer + (i * 8),
+                        ctx->loclass_mac_buffer + (i * 8) + 4);
+                }
 
-            ctx->state = PicopassEmulatorStateIdle;
+                if(ctx->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
+                    loclass_update_csn(nfc_data, nfcv_data, ctx);
+                    // Only reset the state when we change to a new CSN for the same reason as when we get a standard key
+                    ctx->state = PicopassEmulatorStateIdle;
+                } else {
+                    ctx->state = PicopassEmulatorStateStopEmulation;
+                }
+            }
 
             return;
         }
@@ -1079,7 +1117,7 @@ static void picopass_emu_handle_packet(
         break;
     case RFAL_PICOPASS_CMD_UPDATE: // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2)
         if((nfcv_data->frame_length != 12 && nfcv_data->frame_length != 14) ||
-           ctx->state != PicopassEmulatorStateSelected) {
+           ctx->state != PicopassEmulatorStateSelected || ctx->loclass_mode) {
             return;
         }
 
@@ -1248,9 +1286,6 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
         }
 
         // Setup blocks for loclass attack
-        emu_ctx.key_block_num = 0;
-        loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);
-
         uint8_t conf[8] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
         picopass_emu_write_blocks(nfcv_data, conf, PICOPASS_CONFIG_BLOCK_INDEX, 1);
 
@@ -1260,6 +1295,9 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
         uint8_t aia[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
         picopass_emu_write_blocks(nfcv_data, aia, PICOPASS_SECURE_AIA_BLOCK_INDEX, 1);
 
+        emu_ctx.key_block_num = 0;
+        loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);
+
         loclass_writer_write_start_stop(emu_ctx.loclass_writer, true);
     } else {
         memcpy(nfc_data.uid, blocks[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);