desfire.c 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. #include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
  2. #include "../metroflip_i.h"
  3. #include "desfire.h"
  4. #include <lib/toolbox/strint.h>
  5. #include <stdio.h>
  6. static const MfDesfireApplicationId opal_verify_app_id = {.data = {0x31, 0x45, 0x53}};
  7. static const MfDesfireFileId opal_verify_file_id = 0x07;
  8. static const MfDesfireApplicationId myki_verify_app_id = {.data = {0x00, 0x11, 0xf2}};
  9. static const MfDesfireFileId myki_verify_file_id = 0x0f;
  10. static const MfDesfireApplicationId itso_verify_app_id = {.data = {0x16, 0x02, 0xa0}};
  11. static const MfDesfireFileId itso_verify_file_id = 0x0f;
  12. uint64_t itso_swap_uint64(uint64_t val) {
  13. val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
  14. val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
  15. return (val << 32) | (val >> 32);
  16. }
  17. static const struct {
  18. const MfDesfireApplicationId app;
  19. const char* type;
  20. } clipper_verify_types[] = {
  21. // Application advertised on classic, plastic cards.
  22. {.app = {.data = {0x90, 0x11, 0xf2}}, .type = "Card"},
  23. // Application advertised on a mobile device.
  24. {.app = {.data = {0x91, 0x11, 0xf2}}, .type = "Mobile Device"},
  25. };
  26. static const size_t kNumCardVerifyTypes =
  27. sizeof(clipper_verify_types) / sizeof(clipper_verify_types[0]);
  28. // File ids of important files on the card.
  29. static const MfDesfireFileId clipper_ecash_file_id = 2;
  30. static const MfDesfireFileId clipper_histidx_file_id = 6;
  31. static const MfDesfireFileId clipper_identity_file_id = 8;
  32. static const MfDesfireFileId clipper_history_file_id = 14;
  33. static bool get_file_contents(
  34. const MfDesfireApplication* app,
  35. const MfDesfireFileId* id,
  36. MfDesfireFileType type,
  37. size_t min_size,
  38. const uint8_t** out) {
  39. const MfDesfireFileSettings* settings = mf_desfire_get_file_settings(app, id);
  40. if(settings == NULL) return false;
  41. if(settings->type != type) return false;
  42. const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, id);
  43. if(file_data == NULL) return false;
  44. if(simple_array_get_count(file_data->data) < min_size) return false;
  45. *out = simple_array_cget_data(file_data->data);
  46. return true;
  47. }
  48. struct ClipperVerifyCardInfo_struct {
  49. uint32_t serial_number;
  50. uint16_t counter;
  51. uint16_t last_txn_id;
  52. uint32_t last_updated_tm_1900;
  53. uint16_t last_terminal_id;
  54. int16_t balance_cents;
  55. };
  56. typedef struct ClipperVerifyCardInfo_struct ClipperVerifyCardInfo;
  57. // Opal file 0x7 structure. Assumes a little-endian CPU.
  58. typedef struct FURI_PACKED {
  59. uint32_t serial : 32;
  60. uint8_t check_digit : 4;
  61. bool blocked : 1;
  62. uint16_t txn_number : 16;
  63. int32_t balance : 21;
  64. uint16_t days : 15;
  65. uint16_t minutes : 11;
  66. uint8_t mode : 3;
  67. uint16_t usage : 4;
  68. bool auto_topup : 1;
  69. uint8_t weekly_journeys : 4;
  70. uint16_t checksum : 16;
  71. } OpalVerifyFile;
  72. static_assert(sizeof(OpalVerifyFile) == 16, "OpalFile");
  73. bool opal_verify(const MfDesfireData* data) {
  74. // Check if the card has the expected application
  75. const MfDesfireApplication* app = mf_desfire_get_application(data, &opal_verify_app_id);
  76. if(app == NULL) {
  77. return false;
  78. }
  79. // Verify the file settings: must be of type standard and have the expected size
  80. const MfDesfireFileSettings* file_settings =
  81. mf_desfire_get_file_settings(app, &opal_verify_file_id);
  82. if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
  83. file_settings->data.size != sizeof(OpalVerifyFile)) {
  84. return false;
  85. }
  86. // Check that the file data exists
  87. const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &opal_verify_file_id);
  88. if(file_data == NULL) {
  89. return false;
  90. }
  91. // Retrieve the opal file from the file data
  92. const OpalVerifyFile* opal_file = simple_array_cget_data(file_data->data);
  93. if(opal_file == NULL) {
  94. return false;
  95. }
  96. // Ensure the check digit is valid (i.e. 0..9)
  97. if(opal_file->check_digit > 9) {
  98. return false;
  99. }
  100. // All checks passed, return true
  101. return true;
  102. }
  103. bool myki_verify(const MfDesfireData* data) {
  104. // Check if the card contains the expected Myki application.
  105. const MfDesfireApplication* app = mf_desfire_get_application(data, &myki_verify_app_id);
  106. if(app == NULL) {
  107. return false;
  108. }
  109. // Define the structure for Myki file data.
  110. typedef struct {
  111. uint32_t top;
  112. uint32_t bottom;
  113. } mykiFile;
  114. // Verify file settings: must be present, of the correct type, and large enough to contain a mykiFile.
  115. const MfDesfireFileSettings* file_settings =
  116. mf_desfire_get_file_settings(app, &myki_verify_file_id);
  117. if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
  118. file_settings->data.size < sizeof(mykiFile)) {
  119. return false;
  120. }
  121. // Verify that the file data is available.
  122. const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &myki_verify_file_id);
  123. if(file_data == NULL) {
  124. return false;
  125. }
  126. // Retrieve the Myki file data from the file data array.
  127. const mykiFile* myki_file = simple_array_cget_data(file_data->data);
  128. if(myki_file == NULL) {
  129. return false;
  130. }
  131. // Check that Myki card numbers are prefixed with "308425".
  132. if(myki_file->top != 308425UL) {
  133. return false;
  134. }
  135. // Card numbers are always 15 digits in length.
  136. // The bottom field must be within [10000000, 100000000) to meet this requirement.
  137. if(myki_file->bottom < 10000000UL || myki_file->bottom >= 100000000UL) {
  138. return false;
  139. }
  140. // All checks passed.
  141. return true;
  142. }
  143. bool itso_verify(const MfDesfireData* data) {
  144. // Check if the card contains the expected ITSO application.
  145. const MfDesfireApplication* app = mf_desfire_get_application(data, &itso_verify_app_id);
  146. if(app == NULL) {
  147. return false;
  148. }
  149. // Define the structure for ITSO file data.
  150. typedef struct {
  151. uint64_t part1;
  152. uint64_t part2;
  153. uint64_t part3;
  154. uint64_t part4;
  155. } ItsoFile;
  156. // Verify file settings: must exist, be of standard type,
  157. // and have a data size at least as large as an ItsoFile.
  158. const MfDesfireFileSettings* file_settings =
  159. mf_desfire_get_file_settings(app, &itso_verify_file_id);
  160. if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
  161. file_settings->data.size < sizeof(ItsoFile)) {
  162. return false;
  163. }
  164. // Verify that the file data is available.
  165. const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &itso_verify_file_id);
  166. if(file_data == NULL) {
  167. return false;
  168. }
  169. // Retrieve the ITSO file from the file data.
  170. const ItsoFile* itso_file = simple_array_cget_data(file_data->data);
  171. if(itso_file == NULL) {
  172. return false;
  173. }
  174. // Swap bytes for the first two parts.
  175. uint64_t x1 = itso_swap_uint64(itso_file->part1);
  176. uint64_t x2 = itso_swap_uint64(itso_file->part2);
  177. // Prepare buffers for card and date strings.
  178. char cardBuff[32];
  179. char dateBuff[18];
  180. // Format the hex strings.
  181. snprintf(cardBuff, sizeof(cardBuff), "%llx%llx", x1, x2);
  182. snprintf(dateBuff, sizeof(dateBuff), "%llx", x2);
  183. // Get pointer to the card number substring (skipping the first 4 characters).
  184. char* cardp = cardBuff + 4;
  185. cardp[18] = '\0'; // Ensure the substring is null-terminated.
  186. // Verify that all ITSO card numbers are prefixed with "633597".
  187. if(strncmp(cardp, "633597", 6) != 0) {
  188. return false;
  189. }
  190. // Prepare the date string by advancing 12 characters.
  191. char* datep = dateBuff + 12;
  192. dateBuff[17] = '\0'; // Ensure termination of the date string.
  193. // Convert the date portion (in hexadecimal) to a date stamp.
  194. uint32_t dateStamp;
  195. if(strint_to_uint32(datep, NULL, &dateStamp, 16) != StrintParseNoError) {
  196. return false;
  197. }
  198. // (Optional) Calculate the Unix timestamp if needed:
  199. // uint32_t unixTimestamp = dateStamp * 24 * 60 * 60 + 852076800U;
  200. // All checks passed.
  201. return true;
  202. }
  203. bool clipper_verify(const MfDesfireData* data) {
  204. bool verified = false;
  205. do {
  206. FURI_LOG_I("clipper verify", "verifying..");
  207. const MfDesfireApplication* app = NULL;
  208. // Try each card type until a matching application is found.
  209. for(size_t i = 0; i < kNumCardVerifyTypes; i++) {
  210. app = mf_desfire_get_application(data, &clipper_verify_types[i].app);
  211. if(app != NULL) {
  212. break;
  213. }
  214. }
  215. // If no matching application was found, verification fails.
  216. if(app == NULL) {
  217. break;
  218. }
  219. const uint8_t* id_data;
  220. if(!get_file_contents(
  221. app, &clipper_identity_file_id, MfDesfireFileTypeStandard, 5, &id_data)) {
  222. break;
  223. }
  224. // Get the ecash file contents.
  225. const uint8_t* cash_data;
  226. if(!get_file_contents(
  227. app, &clipper_ecash_file_id, MfDesfireFileTypeBackup, 32, &cash_data)) {
  228. break;
  229. }
  230. // Retrieve ride history file contents.
  231. const uint8_t* history_index;
  232. const uint8_t* history;
  233. if(!get_file_contents(
  234. app, &clipper_histidx_file_id, MfDesfireFileTypeBackup, 16, &history_index)) {
  235. break;
  236. }
  237. if(!get_file_contents(
  238. app, &clipper_history_file_id, MfDesfireFileTypeStandard, 512, &history)) {
  239. break;
  240. }
  241. // Use a dummy string to verify that the ride history can be decoded.
  242. FuriString* dummy_str = furi_string_alloc();
  243. furi_string_free(dummy_str);
  244. verified = true;
  245. } while(false);
  246. return verified;
  247. }