Eric Betts 1 год назад
Родитель
Сommit
8b65648c00
10 измененных файлов с 508 добавлено и 0 удалено
  1. 144 0
      apdu_log.c
  2. 63 0
      apdu_log.h
  3. 178 0
      apdu_runner.c
  4. 10 0
      apdu_runner.h
  5. 78 0
      scenes/seader_scene_apdu_runner.c
  6. 1 0
      scenes/seader_scene_config.h
  7. 12 0
      scenes/seader_scene_sam_present.c
  8. 9 0
      seader_i.h
  9. 8 0
      seader_worker.c
  10. 5 0
      seader_worker.h

+ 144 - 0
apdu_log.c

@@ -0,0 +1,144 @@
+#include "apdu_log.h"
+
+#include <storage/storage.h>
+#include <flipper_format/flipper_format.h>
+#include <toolbox/stream/file_stream.h>
+#include <toolbox/stream/buffered_file_stream.h>
+#include <toolbox/args.h>
+
+#define TAG "APDULog"
+
+struct APDULog {
+    Stream* stream;
+    size_t total_lines;
+};
+
+static inline void apdu_log_add_ending_new_line(APDULog* instance) {
+    if(stream_seek(instance->stream, -1, StreamOffsetFromEnd)) {
+        uint8_t last_char = 0;
+
+        // Check if the last char is new line or add a new line
+        if(stream_read(instance->stream, &last_char, 1) == 1 && last_char != '\n') {
+            FURI_LOG_D(TAG, "Adding new line ending");
+            stream_write_char(instance->stream, '\n');
+        }
+
+        stream_rewind(instance->stream);
+    }
+}
+
+static bool apdu_log_read_log_line(APDULog* instance, FuriString* line, bool* is_endfile) {
+    if(stream_read_line(instance->stream, line) == false) {
+        *is_endfile = true;
+    }
+
+    else {
+        size_t newline_index = furi_string_search_char(line, '\n', 0);
+
+        if(newline_index != FURI_STRING_FAILURE) {
+            furi_string_left(line, newline_index);
+        }
+
+        FURI_LOG_T(
+            TAG, "Read line: %s, len: %zu", furi_string_get_cstr(line), furi_string_size(line));
+
+        return true;
+    }
+
+    return false;
+}
+
+bool apdu_log_check_presence(const char* path) {
+    furi_check(path);
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    bool log_present = storage_common_stat(storage, path, NULL) == FSE_OK;
+
+    furi_record_close(RECORD_STORAGE);
+
+    return log_present;
+}
+
+APDULog* apdu_log_alloc(const char* path, APDULogMode mode) {
+    furi_check(path);
+
+    APDULog* instance = malloc(sizeof(APDULog));
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    instance->stream = buffered_file_stream_alloc(storage);
+
+    FS_OpenMode open_mode = (mode == APDULogModeOpenAlways) ? FSOM_OPEN_ALWAYS :
+                                                              FSOM_OPEN_EXISTING;
+
+    instance->total_lines = 0;
+
+    bool file_exists =
+        buffered_file_stream_open(instance->stream, path, FSAM_READ_WRITE, open_mode);
+
+    if(!file_exists) {
+        buffered_file_stream_close(instance->stream);
+    } else {
+        // Eventually add new line character in the last line to avoid skipping lines
+        apdu_log_add_ending_new_line(instance);
+    }
+
+    FuriString* line = furi_string_alloc();
+
+    bool is_endfile = false;
+
+    // In this loop we only count the entries in the file
+    // We prefer not to load the whole file in memory for space reasons
+    while(file_exists && !is_endfile) {
+        bool read_log = apdu_log_read_log_line(instance, line, &is_endfile);
+        if(read_log) {
+            instance->total_lines++;
+        }
+    }
+    stream_rewind(instance->stream);
+    FURI_LOG_I(TAG, "Loaded log with %zu lines", instance->total_lines);
+
+    furi_string_free(line);
+
+    return instance;
+}
+
+void apdu_log_free(APDULog* instance) {
+    furi_check(instance);
+    furi_check(instance->stream);
+
+    buffered_file_stream_close(instance->stream);
+    stream_free(instance->stream);
+    free(instance);
+
+    furi_record_close(RECORD_STORAGE);
+}
+
+size_t apdu_log_get_total_lines(APDULog* instance) {
+    furi_check(instance);
+
+    return instance->total_lines;
+}
+
+bool apdu_log_rewind(APDULog* instance) {
+    furi_check(instance);
+    furi_check(instance->stream);
+
+    return stream_rewind(instance->stream);
+}
+
+bool apdu_log_get_next_log_str(APDULog* instance, FuriString* log) {
+    furi_assert(instance);
+    furi_assert(instance->stream);
+    furi_assert(log);
+
+    bool log_read = false;
+    bool is_endfile = false;
+
+    furi_string_reset(log);
+
+    while(!log_read && !is_endfile)
+        log_read = apdu_log_read_log_line(instance, log, &is_endfile);
+
+    return log_read;
+}

+ 63 - 0
apdu_log.h

@@ -0,0 +1,63 @@
+#pragma once
+
+#include <flipper_format/flipper_format.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    APDULogModeOpenExisting,
+    APDULogModeOpenAlways,
+} APDULogMode;
+
+typedef struct APDULog APDULog;
+
+/** Check if the file list exists
+ *
+ * @param path      - list path
+ *
+ * @return true if list exists, false otherwise
+*/
+bool apdu_log_check_presence(const char* path);
+
+/** Open or create list
+ * Depending on mode, list will be opened or created.
+ *
+ * @param path      - Path of the file that contain the list
+ * @param mode      - ListKeysMode value
+ *
+ * @return Returns APDULog list instance
+*/
+APDULog* apdu_log_alloc(const char* path, APDULogMode mode);
+
+/** Close list
+ *
+ * @param instance  - APDULog list instance
+*/
+void apdu_log_free(APDULog* instance);
+
+/** Get total number of lines in list
+ *
+ * @param instance  - APDULog list instance
+ *
+ * @return Returns total number of lines in list
+*/
+size_t apdu_log_get_total_lines(APDULog* instance);
+
+/** Rewind list
+ *
+ * @param instance  - APDULog list instance
+ *
+ * @return Returns true if rewind was successful, false otherwise
+*/
+bool apdu_log_rewind(APDULog* instance);
+
+bool apdu_log_get_next_log_str(APDULog* instance, FuriString* log);
+
+#ifdef __cplusplus
+}
+#endif

+ 178 - 0
apdu_runner.c

@@ -0,0 +1,178 @@
+#include "apdu_runner.h"
+
+#define TAG "APDU_Runner"
+
+// Max length of firmware upgrade: 731 bytes
+#define SEADER_APDU_MAX_LEN 732
+
+void seader_apdu_runner_cleanup(Seader* seader, SeaderWorkerEvent event) {
+    SeaderWorker* seader_worker = seader->worker;
+    seader_worker_change_state(seader_worker, SeaderWorkerStateReady);
+    apdu_log_free(seader->apdu_log);
+    seader->apdu_log = NULL;
+    if(seader_worker->callback) {
+        seader_worker->callback(event, seader_worker->context);
+    }
+}
+
+bool seader_apdu_runner_send_next_line(Seader* seader) {
+    SeaderWorker* seader_worker = seader->worker;
+    SeaderUartBridge* seader_uart = seader_worker->uart;
+    SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx);
+
+    FuriString* line = furi_string_alloc();
+    apdu_log_get_next_log_str(seader->apdu_log, line);
+
+    size_t len = furi_string_size(line) / 2; // String is in HEX, divide by 2 for bytes
+    uint8_t* apdu = malloc(len);
+    if(apdu == NULL) {
+        FURI_LOG_E(TAG, "Failed to allocate memory for APDU");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        return false;
+    }
+
+    if(len > SEADER_UART_RX_BUF_SIZE) {
+        FURI_LOG_E(TAG, "APDU length is too long");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        free(apdu);
+        return false;
+    }
+
+    if(!hex_chars_to_uint8(furi_string_get_cstr(line), apdu)) {
+        FURI_LOG_E(TAG, "Failed to convert line to number");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        free(apdu);
+        return false;
+    }
+    FURI_LOG_I(
+        TAG,
+        "APDU Runner => (%d/%d): %s",
+        apdu_runner_ctx->current_line,
+        apdu_runner_ctx->total_lines,
+        furi_string_get_cstr(line));
+
+    if(seader_worker->callback) {
+        seader_worker->callback(SeaderWorkerEventAPDURunnerUpdate, seader_worker->context);
+    }
+
+    apdu_runner_ctx->current_line++;
+    if(seader_uart->T == 1) {
+        seader_send_t1(seader_uart, apdu, len);
+    } else {
+        seader_ccid_XfrBlock(seader_uart, apdu, len);
+    }
+    furi_string_free(line);
+    free(apdu);
+
+    return true;
+}
+
+void seader_apdu_runner_init(Seader* seader) {
+    SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx);
+
+    if(apdu_log_check_presence(SEADER_APDU_RUNNER_FILE_NAME)) {
+        FURI_LOG_I(TAG, "APDU log file exists");
+    } else {
+        FURI_LOG_W(TAG, "APDU log file does not exist");
+        return;
+    }
+
+    seader->apdu_log = apdu_log_alloc(SEADER_APDU_RUNNER_FILE_NAME, APDULogModeOpenExisting);
+    apdu_runner_ctx->current_line = 0;
+    apdu_runner_ctx->total_lines = apdu_log_get_total_lines(seader->apdu_log);
+    FURI_LOG_I(TAG, "APDU log lines: %d", apdu_runner_ctx->total_lines);
+
+    seader_apdu_runner_send_next_line(seader);
+}
+
+bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len) {
+    SeaderUartBridge* seader_uart = seader->worker->uart;
+    SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx);
+    uint8_t GET_RESPONSE[] = {0x00, 0xc0, 0x00, 0x00, 0xff};
+
+    uint8_t SW1 = r_apdu[r_len - 2];
+    uint8_t SW2 = r_apdu[r_len - 1];
+
+    switch(SW1) {
+    case 0x61:
+        //FURI_LOG_D(TAG, "Request %d bytes", SW2);
+        GET_RESPONSE[4] = SW2;
+        seader_ccid_XfrBlock(seader_uart, GET_RESPONSE, sizeof(GET_RESPONSE));
+        return true;
+    }
+
+    if(r_len < SEADER_UART_RX_BUF_SIZE) {
+        char* display = malloc(r_len * 2 + 1);
+        if(display == NULL) {
+            FURI_LOG_E(TAG, "Failed to allocate memory for display");
+            seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+            return false;
+        }
+        memset(display, 0, r_len * 2 + 1);
+        for(uint8_t i = 0; i < r_len; i++) {
+            snprintf(display + (i * 2), sizeof(display), "%02x", r_apdu[i]);
+        }
+        FURI_LOG_I(
+            TAG,
+            "APDU Runner <=: (%d/%d): %s",
+            apdu_runner_ctx->current_line,
+            apdu_runner_ctx->total_lines,
+            display);
+        free(display);
+    } else {
+        FURI_LOG_I(TAG, "APDU Runner <=: Response too long to display");
+    }
+
+    /** Compare last two bytes to expected line **/
+
+    FuriString* line = furi_string_alloc();
+    apdu_log_get_next_log_str(seader->apdu_log, line);
+    if(furi_string_size(line) % 2 == 1) {
+        FURI_LOG_E(TAG, "APDU log file has odd number of characters");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        return false;
+    }
+
+    size_t len = furi_string_size(line) / 2; // String is in HEX, divide by 2 for bytes
+    uint8_t* apdu = malloc(len);
+    if(apdu == NULL) {
+        FURI_LOG_E(TAG, "Failed to allocate memory for APDU");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        return false;
+    }
+
+    if(!hex_chars_to_uint8(furi_string_get_cstr(line), apdu)) {
+        FURI_LOG_E(TAG, "Failed to convert line to byte array");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        free(apdu);
+        // TODO: Send failed event
+        return false;
+    }
+
+    apdu_runner_ctx->current_line++;
+    furi_string_free(line);
+
+    if(memcmp(r_apdu + r_len - 2, apdu + len - 2, 2) != 0) {
+        FURI_LOG_W(
+            TAG,
+            "APDU runner response does not match.  Response %02x%02x != expected %02x%02x",
+            r_apdu[r_len - 2],
+            r_apdu[r_len - 1],
+            apdu[len - 2],
+            apdu[len - 1]);
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        free(apdu);
+        return false;
+    }
+    free(apdu);
+
+    // Check if we are at the end of the log
+    if(apdu_runner_ctx->current_line >= apdu_runner_ctx->total_lines) {
+        FURI_LOG_I(TAG, "APDU runner finished");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerSuccess);
+        return false;
+    }
+
+    // Send next line
+    return seader_apdu_runner_send_next_line(seader);
+}

+ 10 - 0
apdu_runner.h

@@ -0,0 +1,10 @@
+
+#pragma once
+
+#include <lib/toolbox/hex.h>
+#include "seader_i.h"
+
+#define SEADER_APDU_RUNNER_FILE_NAME APP_DATA_PATH("script.apdu")
+
+void seader_apdu_runner_init(Seader* seader);
+bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len);

+ 78 - 0
scenes/seader_scene_apdu_runner.c

@@ -0,0 +1,78 @@
+#include "../seader_i.h"
+#include <dolphin/dolphin.h>
+
+#define TAG "Seader:Scene:APDURunner"
+
+char seader_scene_apdu_runner_update_text[24];
+
+void seader_apdu_runner_worker_callback(SeaderWorkerEvent event, void* context) {
+    Seader* seader = context;
+    view_dispatcher_send_custom_event(seader->view_dispatcher, event);
+}
+
+void seader_scene_apdu_runner_on_enter(void* context) {
+    Seader* seader = context;
+    // Setup view
+    Popup* popup = seader->popup;
+    popup_set_header(popup, "APDU Runner", 68, 30, AlignLeft, AlignTop);
+
+    // TODO: Make icon for interaction with SAM
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
+
+    // Start worker
+    seader_worker_start(
+        seader->worker,
+        SeaderWorkerStateAPDURunner,
+        seader->uart,
+        seader_apdu_runner_worker_callback,
+        seader);
+
+    view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewPopup);
+}
+
+bool seader_scene_apdu_runner_on_event(void* context, SceneManagerEvent event) {
+    Seader* seader = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            scene_manager_search_and_switch_to_previous_scene(
+                seader->scene_manager, SeaderSceneSamPresent);
+            consumed = true;
+        } else if(event.event == SeaderWorkerEventAPDURunnerUpdate) {
+            SeaderAPDURunnerContext apdu_runner_ctx = seader->apdu_runner_ctx;
+            Popup* popup = seader->popup;
+            snprintf(
+                seader_scene_apdu_runner_update_text,
+                sizeof(seader_scene_apdu_runner_update_text),
+                "APDU Runner\n%d/%d",
+                apdu_runner_ctx.current_line,
+                apdu_runner_ctx.total_lines);
+            popup_set_header(
+                popup, seader_scene_apdu_runner_update_text, 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == SeaderWorkerEventAPDURunnerSuccess) {
+            notification_message(seader->notifications, &sequence_success);
+            Popup* popup = seader->popup;
+            popup_set_header(popup, "APDU Runner\nSuccess", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == SeaderWorkerEventAPDURunnerError) {
+            notification_message(seader->notifications, &sequence_error);
+            Popup* popup = seader->popup;
+            popup_set_header(popup, "APDU Runner\nError", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_search_and_switch_to_previous_scene(
+            seader->scene_manager, SeaderSceneSamPresent);
+        consumed = true;
+    }
+    return consumed;
+}
+
+void seader_scene_apdu_runner_on_exit(void* context) {
+    Seader* seader = context;
+
+    // Clear view
+    popup_reset(seader->popup);
+}

+ 1 - 0
scenes/seader_scene_config.h

@@ -17,3 +17,4 @@ ADD_SCENE(seader, sam_info, SamInfo)
 ADD_SCENE(seader, virtual_credential, VirtualCredential)
 ADD_SCENE(seader, formats, Formats)
 ADD_SCENE(seader, read_mfc, ReadMfc)
+ADD_SCENE(seader, apdu_runner, APDURunner)

+ 12 - 0
scenes/seader_scene_sam_present.c

@@ -4,6 +4,7 @@ enum SubmenuIndex {
     SubmenuIndexRead14a,
     SubmenuIndexReadMfc,
     SubmenuIndexSaved,
+    SubmenuIndexAPDURunner,
     SubmenuIndexSamInfo,
     SubmenuIndexFwVersion,
 };
@@ -43,6 +44,14 @@ void seader_scene_sam_present_on_update(void* context) {
     submenu_add_item(
         submenu, "Saved", SubmenuIndexSaved, seader_scene_sam_present_submenu_callback, seader);
 
+    if(apdu_log_check_presence(SEADER_APDU_RUNNER_FILE_NAME)) {
+        submenu_add_item(
+            submenu,
+            "Run APDUs",
+            SubmenuIndexAPDURunner,
+            seader_scene_sam_present_submenu_callback,
+            seader);
+    }
     if(seader_worker->sam_version[0] != 0 && seader_worker->sam_version[1] != 0) {
         FuriString* fw_str = furi_string_alloc();
         furi_string_cat_printf(
@@ -100,6 +109,9 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) {
         } else if(event.event == SeaderWorkerEventSamMissing) {
             scene_manager_next_scene(seader->scene_manager, SeaderSceneSamMissing);
             consumed = true;
+        } else if(event.event == SubmenuIndexAPDURunner) {
+            scene_manager_next_scene(seader->scene_manager, SeaderSceneAPDURunner);
+            consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_stop(seader->scene_manager);

+ 9 - 0
seader_i.h

@@ -54,6 +54,7 @@
 #include "t_1.h"
 #include "seader_worker.h"
 #include "seader_credential.h"
+#include "apdu_log.h"
 
 #define WORKER_ALL_RX_EVENTS                                                      \
     (WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \
@@ -89,6 +90,11 @@ typedef enum {
     WorkerEvtCtrlLineSet = (1 << 7),
 } WorkerEvtFlags;
 
+typedef struct {
+    uint16_t total_lines;
+    uint16_t current_line;
+} SeaderAPDURunnerContext;
+
 struct Seader {
     bool revert_power;
     bool is_debug_enabled;
@@ -120,6 +126,9 @@ struct Seader {
 
     PluginManager* plugin_manager;
     PluginWiegand* plugin_wiegand;
+
+    APDULog* apdu_log;
+    SeaderAPDURunnerContext apdu_runner_ctx;
 };
 
 struct SeaderPollerContainer {

+ 8 - 0
seader_worker.c

@@ -116,6 +116,10 @@ bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t l
         return false;
     }
 
+    if(seader_worker->state == SeaderWorkerStateAPDURunner) {
+        return seader_apdu_runner_response(seader, apdu, len);
+    }
+
     char* display = malloc(len * 2 + 1);
     memset(display, 0, len * 2 + 1);
     for(uint8_t i = 0; i < len; i++) {
@@ -211,6 +215,10 @@ int32_t seader_worker_task(void* context) {
     } else if(seader_worker->state == SeaderWorkerStateVirtualCredential) {
         FURI_LOG_D(TAG, "Virtual Credential");
         seader_worker_virtual_credential(seader);
+    } else if(seader_worker->state == SeaderWorkerStateAPDURunner) {
+        FURI_LOG_D(TAG, "APDU Runner");
+        seader_apdu_runner_init(seader);
+        return 0;
     }
     seader_worker_change_state(seader_worker, SeaderWorkerStateReady);
 

+ 5 - 0
seader_worker.h

@@ -6,6 +6,7 @@
 #include "sam_api.h"
 #include "seader_credential.h"
 #include "seader_bridge.h"
+#include "apdu_runner.h"
 
 typedef struct SeaderWorker SeaderWorker;
 typedef struct CCID_Message CCID_Message;
@@ -19,6 +20,7 @@ typedef enum {
     // Main worker states
     SeaderWorkerStateCheckSam,
     SeaderWorkerStateVirtualCredential,
+    SeaderWorkerStateAPDURunner,
     // Transition
     SeaderWorkerStateStop,
 } SeaderWorkerState;
@@ -35,6 +37,9 @@ typedef enum {
     SeaderWorkerEventSamMissing,
     SeaderWorkerEventNoCardDetected,
     SeaderWorkerEventStartReading,
+    SeaderWorkerEventAPDURunnerUpdate,
+    SeaderWorkerEventAPDURunnerSuccess,
+    SeaderWorkerEventAPDURunnerError,
 } SeaderWorkerEvent;
 
 typedef enum {