| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- #include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
- #include "../metroflip_i.h"
- #include "desfire.h"
- #include <lib/toolbox/strint.h>
- #include <stdio.h>
- static const MfDesfireApplicationId opal_verify_app_id = {.data = {0x31, 0x45, 0x53}};
- static const MfDesfireFileId opal_verify_file_id = 0x07;
- static const MfDesfireApplicationId myki_verify_app_id = {.data = {0x00, 0x11, 0xf2}};
- static const MfDesfireFileId myki_verify_file_id = 0x0f;
- static const MfDesfireApplicationId itso_verify_app_id = {.data = {0x16, 0x02, 0xa0}};
- static const MfDesfireFileId itso_verify_file_id = 0x0f;
- uint64_t itso_swap_uint64(uint64_t val) {
- val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
- val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
- return (val << 32) | (val >> 32);
- }
- static const struct {
- const MfDesfireApplicationId app;
- const char* type;
- } clipper_verify_types[] = {
- // Application advertised on classic, plastic cards.
- {.app = {.data = {0x90, 0x11, 0xf2}}, .type = "Card"},
- // Application advertised on a mobile device.
- {.app = {.data = {0x91, 0x11, 0xf2}}, .type = "Mobile Device"},
- };
- static const size_t kNumCardVerifyTypes =
- sizeof(clipper_verify_types) / sizeof(clipper_verify_types[0]);
- // File ids of important files on the card.
- static const MfDesfireFileId clipper_ecash_file_id = 2;
- static const MfDesfireFileId clipper_histidx_file_id = 6;
- static const MfDesfireFileId clipper_identity_file_id = 8;
- static const MfDesfireFileId clipper_history_file_id = 14;
- static bool get_file_contents(
- const MfDesfireApplication* app,
- const MfDesfireFileId* id,
- MfDesfireFileType type,
- size_t min_size,
- const uint8_t** out) {
- const MfDesfireFileSettings* settings = mf_desfire_get_file_settings(app, id);
- if(settings == NULL) return false;
- if(settings->type != type) return false;
- const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, id);
- if(file_data == NULL) return false;
- if(simple_array_get_count(file_data->data) < min_size) return false;
- *out = simple_array_cget_data(file_data->data);
- return true;
- }
- struct ClipperVerifyCardInfo_struct {
- uint32_t serial_number;
- uint16_t counter;
- uint16_t last_txn_id;
- uint32_t last_updated_tm_1900;
- uint16_t last_terminal_id;
- int16_t balance_cents;
- };
- typedef struct ClipperVerifyCardInfo_struct ClipperVerifyCardInfo;
- // Opal file 0x7 structure. Assumes a little-endian CPU.
- typedef struct FURI_PACKED {
- uint32_t serial : 32;
- uint8_t check_digit : 4;
- bool blocked : 1;
- uint16_t txn_number : 16;
- int32_t balance : 21;
- uint16_t days : 15;
- uint16_t minutes : 11;
- uint8_t mode : 3;
- uint16_t usage : 4;
- bool auto_topup : 1;
- uint8_t weekly_journeys : 4;
- uint16_t checksum : 16;
- } OpalVerifyFile;
- static_assert(sizeof(OpalVerifyFile) == 16, "OpalFile");
- bool opal_verify(const MfDesfireData* data) {
- // Check if the card has the expected application
- const MfDesfireApplication* app = mf_desfire_get_application(data, &opal_verify_app_id);
- if(app == NULL) {
- return false;
- }
- // Verify the file settings: must be of type standard and have the expected size
- const MfDesfireFileSettings* file_settings =
- mf_desfire_get_file_settings(app, &opal_verify_file_id);
- if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
- file_settings->data.size != sizeof(OpalVerifyFile)) {
- return false;
- }
- // Check that the file data exists
- const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &opal_verify_file_id);
- if(file_data == NULL) {
- return false;
- }
- // Retrieve the opal file from the file data
- const OpalVerifyFile* opal_file = simple_array_cget_data(file_data->data);
- if(opal_file == NULL) {
- return false;
- }
- // Ensure the check digit is valid (i.e. 0..9)
- if(opal_file->check_digit > 9) {
- return false;
- }
- // All checks passed, return true
- return true;
- }
- bool myki_verify(const MfDesfireData* data) {
- // Check if the card contains the expected Myki application.
- const MfDesfireApplication* app = mf_desfire_get_application(data, &myki_verify_app_id);
- if(app == NULL) {
- return false;
- }
- // Define the structure for Myki file data.
- typedef struct {
- uint32_t top;
- uint32_t bottom;
- } mykiFile;
- // Verify file settings: must be present, of the correct type, and large enough to contain a mykiFile.
- const MfDesfireFileSettings* file_settings =
- mf_desfire_get_file_settings(app, &myki_verify_file_id);
- if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
- file_settings->data.size < sizeof(mykiFile)) {
- return false;
- }
- // Verify that the file data is available.
- const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &myki_verify_file_id);
- if(file_data == NULL) {
- return false;
- }
- // Retrieve the Myki file data from the file data array.
- const mykiFile* myki_file = simple_array_cget_data(file_data->data);
- if(myki_file == NULL) {
- return false;
- }
- // Check that Myki card numbers are prefixed with "308425".
- if(myki_file->top != 308425UL) {
- return false;
- }
- // Card numbers are always 15 digits in length.
- // The bottom field must be within [10000000, 100000000) to meet this requirement.
- if(myki_file->bottom < 10000000UL || myki_file->bottom >= 100000000UL) {
- return false;
- }
- // All checks passed.
- return true;
- }
- bool itso_verify(const MfDesfireData* data) {
- // Check if the card contains the expected ITSO application.
- const MfDesfireApplication* app = mf_desfire_get_application(data, &itso_verify_app_id);
- if(app == NULL) {
- return false;
- }
- // Define the structure for ITSO file data.
- typedef struct {
- uint64_t part1;
- uint64_t part2;
- uint64_t part3;
- uint64_t part4;
- } ItsoFile;
- // Verify file settings: must exist, be of standard type,
- // and have a data size at least as large as an ItsoFile.
- const MfDesfireFileSettings* file_settings =
- mf_desfire_get_file_settings(app, &itso_verify_file_id);
- if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
- file_settings->data.size < sizeof(ItsoFile)) {
- return false;
- }
- // Verify that the file data is available.
- const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &itso_verify_file_id);
- if(file_data == NULL) {
- return false;
- }
- // Retrieve the ITSO file from the file data.
- const ItsoFile* itso_file = simple_array_cget_data(file_data->data);
- if(itso_file == NULL) {
- return false;
- }
- // Swap bytes for the first two parts.
- uint64_t x1 = itso_swap_uint64(itso_file->part1);
- uint64_t x2 = itso_swap_uint64(itso_file->part2);
- // Prepare buffers for card and date strings.
- char cardBuff[32];
- char dateBuff[18];
- // Format the hex strings.
- snprintf(cardBuff, sizeof(cardBuff), "%llx%llx", x1, x2);
- snprintf(dateBuff, sizeof(dateBuff), "%llx", x2);
- // Get pointer to the card number substring (skipping the first 4 characters).
- char* cardp = cardBuff + 4;
- cardp[18] = '\0'; // Ensure the substring is null-terminated.
- // Verify that all ITSO card numbers are prefixed with "633597".
- if(strncmp(cardp, "633597", 6) != 0) {
- return false;
- }
- // Prepare the date string by advancing 12 characters.
- char* datep = dateBuff + 12;
- dateBuff[17] = '\0'; // Ensure termination of the date string.
- // Convert the date portion (in hexadecimal) to a date stamp.
- uint32_t dateStamp;
- if(strint_to_uint32(datep, NULL, &dateStamp, 16) != StrintParseNoError) {
- return false;
- }
- // (Optional) Calculate the Unix timestamp if needed:
- // uint32_t unixTimestamp = dateStamp * 24 * 60 * 60 + 852076800U;
- // All checks passed.
- return true;
- }
- bool clipper_verify(const MfDesfireData* data) {
- bool verified = false;
- do {
- FURI_LOG_I("clipper verify", "verifying..");
- const MfDesfireApplication* app = NULL;
- // Try each card type until a matching application is found.
- for(size_t i = 0; i < kNumCardVerifyTypes; i++) {
- app = mf_desfire_get_application(data, &clipper_verify_types[i].app);
- if(app != NULL) {
- break;
- }
- }
- // If no matching application was found, verification fails.
- if(app == NULL) {
- break;
- }
- const uint8_t* id_data;
- if(!get_file_contents(
- app, &clipper_identity_file_id, MfDesfireFileTypeStandard, 5, &id_data)) {
- break;
- }
- // Get the ecash file contents.
- const uint8_t* cash_data;
- if(!get_file_contents(
- app, &clipper_ecash_file_id, MfDesfireFileTypeBackup, 32, &cash_data)) {
- break;
- }
- // Retrieve ride history file contents.
- const uint8_t* history_index;
- const uint8_t* history;
- if(!get_file_contents(
- app, &clipper_histidx_file_id, MfDesfireFileTypeBackup, 16, &history_index)) {
- break;
- }
- if(!get_file_contents(
- app, &clipper_history_file_id, MfDesfireFileTypeStandard, 512, &history)) {
- break;
- }
- // Use a dummy string to verify that the ride history can be decoded.
- FuriString* dummy_str = furi_string_alloc();
- furi_string_free(dummy_str);
- verified = true;
- } while(false);
- return verified;
- }
|