passy_reader.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. #include "passy_reader.h"
  2. #define TAG "PassyReader"
  3. #define PASSY_READER_DG1_CHUNK_SIZE 0x20
  4. #define PASSY_READER_DG2_CHUNK_SIZE 0x20
  5. static uint8_t passport_aid[] = {0xA0, 0x00, 0x00, 0x02, 0x47, 0x10, 0x01};
  6. static uint8_t select_header[] = {0x00, 0xA4, 0x04, 0x0C};
  7. static uint8_t get_challenge[] = {0x00, 0x84, 0x00, 0x00, 0x08};
  8. static uint8_t SW_success[] = {0x90, 0x00};
  9. size_t asn1_length(uint8_t data[3]) {
  10. if(data[0] <= 0x7F) {
  11. return data[0];
  12. } else if(data[0] == 0x81) {
  13. return data[1];
  14. } else if(data[0] == 0x82) {
  15. return (data[1] << 8) | data[2];
  16. }
  17. return 0;
  18. }
  19. PassyReader* passy_reader_alloc(Passy* passy, Iso14443_4bPoller* iso14443_4b_poller) {
  20. PassyReader* passy_reader = malloc(sizeof(PassyReader));
  21. memset(passy_reader, 0, sizeof(PassyReader));
  22. passy_reader->iso14443_4b_poller = iso14443_4b_poller;
  23. passy_reader->DG1 = passy->DG1;
  24. passy_reader->tx_buffer = bit_buffer_alloc(PASSY_READER_MAX_BUFFER_SIZE);
  25. passy_reader->rx_buffer = bit_buffer_alloc(PASSY_READER_MAX_BUFFER_SIZE);
  26. char passport_number[11];
  27. memset(passport_number, 0, sizeof(passport_number));
  28. memcpy(passport_number, passy->passport_number, strlen(passy->passport_number));
  29. passport_number[strlen(passy->passport_number)] = passy_checksum(passy->passport_number);
  30. FURI_LOG_I(TAG, "Passport number: %s", passport_number);
  31. char date_of_birth[8];
  32. memset(date_of_birth, 0, sizeof(date_of_birth));
  33. memcpy(date_of_birth, passy->date_of_birth, strlen(passy->date_of_birth));
  34. date_of_birth[strlen(passy->date_of_birth)] = passy_checksum(passy->date_of_birth);
  35. FURI_LOG_I(TAG, "Date of birth: %s", date_of_birth);
  36. char date_of_expiry[8];
  37. memset(date_of_expiry, 0, sizeof(date_of_expiry));
  38. memcpy(date_of_expiry, passy->date_of_expiry, strlen(passy->date_of_expiry));
  39. date_of_expiry[strlen(passy->date_of_expiry)] = passy_checksum(passy->date_of_expiry);
  40. FURI_LOG_I(TAG, "Date of expiry: %s", date_of_expiry);
  41. passy_reader->secure_messaging = secure_messaging_alloc(
  42. (uint8_t*)passport_number, (uint8_t*)date_of_birth, (uint8_t*)date_of_expiry);
  43. return passy_reader;
  44. }
  45. void passy_reader_free(PassyReader* passy_reader) {
  46. furi_assert(passy_reader);
  47. bit_buffer_free(passy_reader->tx_buffer);
  48. bit_buffer_free(passy_reader->rx_buffer);
  49. if(passy_reader->secure_messaging) {
  50. secure_messaging_free(passy_reader->secure_messaging);
  51. }
  52. free(passy_reader);
  53. }
  54. NfcCommand passy_reader_select_application(PassyReader* passy_reader) {
  55. NfcCommand ret = NfcCommandContinue;
  56. BitBuffer* tx_buffer = passy_reader->tx_buffer;
  57. BitBuffer* rx_buffer = passy_reader->rx_buffer;
  58. Iso14443_4bPoller* iso14443_4b_poller = passy_reader->iso14443_4b_poller;
  59. Iso14443_4bError error;
  60. bit_buffer_append_bytes(tx_buffer, select_header, sizeof(select_header));
  61. bit_buffer_append_byte(tx_buffer, sizeof(passport_aid));
  62. bit_buffer_append_bytes(tx_buffer, passport_aid, sizeof(passport_aid));
  63. bit_buffer_append_byte(tx_buffer, 0x00); // Le
  64. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  65. if(error != Iso14443_4bErrorNone) {
  66. FURI_LOG_W(TAG, "iso14443_4b_poller_send_block error %d", error);
  67. return NfcCommandStop;
  68. }
  69. bit_buffer_reset(tx_buffer);
  70. passy_log_bitbuffer(TAG, "NFC response", rx_buffer);
  71. // Check SW
  72. size_t length = bit_buffer_get_size_bytes(rx_buffer);
  73. const uint8_t* data = bit_buffer_get_data(rx_buffer);
  74. if(length < 2) {
  75. FURI_LOG_W(TAG, "Invalid response length %d", length);
  76. return NfcCommandStop;
  77. }
  78. if(memcmp(data + length - 2, SW_success, sizeof(SW_success)) != 0) {
  79. FURI_LOG_W(TAG, "Invalid SW %02x %02x", data[length - 2], data[length - 1]);
  80. return NfcCommandStop;
  81. }
  82. return ret;
  83. }
  84. NfcCommand passy_reader_get_challenge(PassyReader* passy_reader) {
  85. NfcCommand ret = NfcCommandContinue;
  86. BitBuffer* tx_buffer = passy_reader->tx_buffer;
  87. BitBuffer* rx_buffer = passy_reader->rx_buffer;
  88. Iso14443_4bPoller* iso14443_4b_poller = passy_reader->iso14443_4b_poller;
  89. Iso14443_4bError error;
  90. bit_buffer_append_bytes(tx_buffer, get_challenge, sizeof(get_challenge));
  91. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  92. if(error != Iso14443_4bErrorNone) {
  93. FURI_LOG_W(TAG, "iso14443_4b_poller_send_block error %d", error);
  94. return NfcCommandStop;
  95. }
  96. bit_buffer_reset(tx_buffer);
  97. passy_log_bitbuffer(TAG, "NFC response", rx_buffer);
  98. // Check SW
  99. size_t length = bit_buffer_get_size_bytes(rx_buffer);
  100. const uint8_t* data = bit_buffer_get_data(rx_buffer);
  101. if(length < 2) {
  102. FURI_LOG_W(TAG, "Invalid response length %d", length);
  103. return NfcCommandStop;
  104. }
  105. if(memcmp(data + length - 2, SW_success, sizeof(SW_success)) != 0) {
  106. FURI_LOG_W(TAG, "Invalid SW %02x %02x", data[length - 2], data[length - 1]);
  107. return NfcCommandStop;
  108. }
  109. SecureMessaging* secure_messaging = passy_reader->secure_messaging;
  110. const uint8_t* rnd_icc = data;
  111. memcpy(secure_messaging->rndICC, rnd_icc, 8);
  112. return ret;
  113. }
  114. NfcCommand passy_reader_authenticate(PassyReader* passy_reader) {
  115. NfcCommand ret = NfcCommandContinue;
  116. BitBuffer* tx_buffer = passy_reader->tx_buffer;
  117. BitBuffer* rx_buffer = passy_reader->rx_buffer;
  118. Iso14443_4bPoller* iso14443_4b_poller = passy_reader->iso14443_4b_poller;
  119. Iso14443_4bError error;
  120. // TODO: move into secure_messaging
  121. SecureMessaging* secure_messaging = passy_reader->secure_messaging;
  122. uint8_t S[32];
  123. memset(S, 0, sizeof(S));
  124. uint8_t eifd[32];
  125. memcpy(S, secure_messaging->rndIFD, sizeof(secure_messaging->rndIFD));
  126. memcpy(
  127. S + sizeof(secure_messaging->rndIFD),
  128. secure_messaging->rndICC,
  129. sizeof(secure_messaging->rndICC));
  130. memcpy(
  131. S + sizeof(secure_messaging->rndIFD) + sizeof(secure_messaging->rndICC),
  132. secure_messaging->Kifd,
  133. sizeof(secure_messaging->Kifd));
  134. uint8_t iv[8];
  135. memset(iv, 0, sizeof(iv));
  136. mbedtls_des3_context ctx;
  137. mbedtls_des3_init(&ctx);
  138. mbedtls_des3_set2key_enc(&ctx, secure_messaging->KENC);
  139. mbedtls_des3_crypt_cbc(&ctx, MBEDTLS_DES_ENCRYPT, sizeof(S), iv, S, eifd);
  140. mbedtls_des3_free(&ctx);
  141. passy_log_buffer(TAG, "S", S, sizeof(S));
  142. passy_log_buffer(TAG, "eifd", eifd, sizeof(eifd));
  143. uint8_t mifd[8];
  144. passy_mac(secure_messaging->KMAC, eifd, sizeof(eifd), mifd, false);
  145. passy_log_buffer(TAG, "mifd", mifd, sizeof(mifd));
  146. uint8_t authenticate_header[] = {0x00, 0x82, 0x00, 0x00};
  147. bit_buffer_append_bytes(tx_buffer, authenticate_header, sizeof(authenticate_header));
  148. bit_buffer_append_byte(tx_buffer, sizeof(eifd) + sizeof(mifd));
  149. bit_buffer_append_bytes(tx_buffer, eifd, sizeof(eifd));
  150. bit_buffer_append_bytes(tx_buffer, mifd, sizeof(mifd));
  151. bit_buffer_append_byte(tx_buffer, 0x28); // Le
  152. passy_log_bitbuffer(TAG, "NFC transmit", tx_buffer);
  153. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  154. if(error != Iso14443_4bErrorNone) {
  155. FURI_LOG_W(TAG, "iso14443_4b_poller_send_block error %d", error);
  156. return NfcCommandStop;
  157. }
  158. bit_buffer_reset(tx_buffer);
  159. passy_log_bitbuffer(TAG, "NFC response", rx_buffer);
  160. // Check SW
  161. size_t length = bit_buffer_get_size_bytes(rx_buffer);
  162. const uint8_t* data = bit_buffer_get_data(rx_buffer);
  163. if(length < 2) {
  164. FURI_LOG_W(TAG, "Invalid response length %d", length);
  165. return NfcCommandStop;
  166. }
  167. if(memcmp(data + length - 2, SW_success, sizeof(SW_success)) != 0) {
  168. FURI_LOG_W(TAG, "Invalid SW %02x %02x", data[length - 2], data[length - 1]);
  169. return NfcCommandStop;
  170. }
  171. const uint8_t* mac = data + length - 2 - 8;
  172. uint8_t calculated_mac[8];
  173. passy_mac(secure_messaging->KMAC, (uint8_t*)data, length - 8 - 2, calculated_mac, false);
  174. if(memcmp(mac, calculated_mac, sizeof(calculated_mac)) != 0) {
  175. FURI_LOG_W(TAG, "Invalid MAC");
  176. return NfcCommandStop;
  177. }
  178. uint8_t decrypted[32];
  179. do {
  180. uint8_t iv[8];
  181. memset(iv, 0, sizeof(iv));
  182. mbedtls_des3_context ctx;
  183. mbedtls_des3_init(&ctx);
  184. mbedtls_des3_set2key_dec(&ctx, secure_messaging->KENC);
  185. mbedtls_des3_crypt_cbc(&ctx, MBEDTLS_DES_DECRYPT, length - 2 - 8, iv, data, decrypted);
  186. mbedtls_des3_free(&ctx);
  187. } while(false);
  188. passy_log_buffer(TAG, "decrypted", decrypted, sizeof(decrypted));
  189. uint8_t* rnd_icc = decrypted;
  190. uint8_t* rnd_ifd = decrypted + 8;
  191. uint8_t* Kicc = decrypted + 16;
  192. if(memcmp(rnd_icc, secure_messaging->rndICC, sizeof(secure_messaging->rndICC)) != 0) {
  193. FURI_LOG_W(TAG, "Invalid rndICC");
  194. return NfcCommandStop;
  195. }
  196. memcpy(secure_messaging->Kicc, Kicc, sizeof(secure_messaging->Kicc));
  197. memcpy(secure_messaging->SSC + 0, rnd_icc + 4, 4);
  198. memcpy(secure_messaging->SSC + 4, rnd_ifd + 4, 4);
  199. return ret;
  200. }
  201. NfcCommand passy_reader_select_file(PassyReader* passy_reader, uint16_t file_id) {
  202. NfcCommand ret = NfcCommandContinue;
  203. BitBuffer* tx_buffer = passy_reader->tx_buffer;
  204. BitBuffer* rx_buffer = passy_reader->rx_buffer;
  205. Iso14443_4bPoller* iso14443_4b_poller = passy_reader->iso14443_4b_poller;
  206. Iso14443_4bError error;
  207. uint8_t select_0101[] = {0x00, 0xa4, 0x02, 0x0c, 0x02, 0x00, 0x00};
  208. select_0101[5] = (file_id >> 8) & 0xFF;
  209. select_0101[6] = file_id & 0xFF;
  210. secure_messaging_wrap_apdu(
  211. passy_reader->secure_messaging, select_0101, sizeof(select_0101), tx_buffer);
  212. passy_log_bitbuffer(TAG, "NFC transmit", tx_buffer);
  213. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  214. if(error != Iso14443_4bErrorNone) {
  215. FURI_LOG_W(TAG, "iso14443_4b_poller_send_block error %d", error);
  216. return NfcCommandStop;
  217. }
  218. bit_buffer_reset(tx_buffer);
  219. passy_log_bitbuffer(TAG, "NFC response", rx_buffer);
  220. // Check SW
  221. size_t length = bit_buffer_get_size_bytes(rx_buffer);
  222. const uint8_t* data = bit_buffer_get_data(rx_buffer);
  223. if(length < 2) {
  224. FURI_LOG_W(TAG, "Invalid response length %d", length);
  225. return NfcCommandStop;
  226. }
  227. if(memcmp(data + length - 2, SW_success, sizeof(SW_success)) != 0) {
  228. FURI_LOG_W(TAG, "Invalid SW %02x %02x", data[length - 2], data[length - 1]);
  229. return NfcCommandStop;
  230. }
  231. secure_messaging_unwrap_rapdu(passy_reader->secure_messaging, rx_buffer);
  232. passy_log_bitbuffer(TAG, "NFC response (decrypted)", rx_buffer);
  233. return ret;
  234. }
  235. NfcCommand passy_reader_read_binary(
  236. PassyReader* passy_reader,
  237. uint16_t offset,
  238. uint8_t Le,
  239. uint8_t* output_buffer) {
  240. NfcCommand ret = NfcCommandContinue;
  241. BitBuffer* tx_buffer = passy_reader->tx_buffer;
  242. BitBuffer* rx_buffer = passy_reader->rx_buffer;
  243. Iso14443_4bPoller* iso14443_4b_poller = passy_reader->iso14443_4b_poller;
  244. Iso14443_4bError error;
  245. if(offset & 0x8000) {
  246. FURI_LOG_W(TAG, "Invalid offset %04x", offset);
  247. }
  248. uint8_t read_binary[] = {0x00, 0xB0, (offset >> 8) & 0xff, (offset >> 0) & 0xff, Le};
  249. secure_messaging_wrap_apdu(
  250. passy_reader->secure_messaging, read_binary, sizeof(read_binary), tx_buffer);
  251. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  252. if(error != Iso14443_4bErrorNone) {
  253. FURI_LOG_W(TAG, "iso14443_4b_poller_send_block error %d", error);
  254. return NfcCommandStop;
  255. }
  256. bit_buffer_reset(tx_buffer);
  257. // Check SW
  258. size_t length = bit_buffer_get_size_bytes(rx_buffer);
  259. const uint8_t* data = bit_buffer_get_data(rx_buffer);
  260. if(length < 2) {
  261. FURI_LOG_W(TAG, "Invalid response length %d", length);
  262. return NfcCommandStop;
  263. }
  264. if(memcmp(data + length - 2, SW_success, sizeof(SW_success)) != 0) {
  265. FURI_LOG_W(TAG, "Invalid SW %02x %02x", data[length - 2], data[length - 1]);
  266. return NfcCommandStop;
  267. }
  268. secure_messaging_unwrap_rapdu(passy_reader->secure_messaging, rx_buffer);
  269. const uint8_t* decrypted_data = bit_buffer_get_data(rx_buffer);
  270. memcpy(output_buffer, decrypted_data, Le);
  271. return ret;
  272. }
  273. NfcCommand passy_reader_state_machine(Passy* passy, PassyReader* passy_reader) {
  274. furi_assert(passy_reader);
  275. NfcCommand ret = NfcCommandContinue;
  276. do {
  277. ret = passy_reader_select_application(passy_reader);
  278. if(ret != NfcCommandContinue) {
  279. view_dispatcher_send_custom_event(passy->view_dispatcher, PassyCustomEventReaderError);
  280. break;
  281. }
  282. ret = passy_reader_get_challenge(passy_reader);
  283. if(ret != NfcCommandContinue) {
  284. view_dispatcher_send_custom_event(passy->view_dispatcher, PassyCustomEventReaderError);
  285. break;
  286. }
  287. ret = passy_reader_authenticate(passy_reader);
  288. if(ret != NfcCommandContinue) {
  289. view_dispatcher_send_custom_event(passy->view_dispatcher, PassyCustomEventReaderError);
  290. break;
  291. }
  292. FURI_LOG_I(TAG, "Mututal authentication success");
  293. secure_messaging_calculate_session_keys(passy_reader->secure_messaging);
  294. view_dispatcher_send_custom_event(
  295. passy->view_dispatcher, PassyCustomEventReaderAuthenticated);
  296. ret = passy_reader_select_file(passy_reader, 0x0101);
  297. if(ret != NfcCommandContinue) {
  298. view_dispatcher_send_custom_event(passy->view_dispatcher, PassyCustomEventReaderError);
  299. break;
  300. }
  301. if(passy->read_type == PassyReadDG1) {
  302. bit_buffer_reset(passy->DG1);
  303. uint8_t header[4];
  304. ret = passy_reader_read_binary(passy_reader, 0x00, 0x04, header);
  305. if(ret != NfcCommandContinue) {
  306. view_dispatcher_send_custom_event(
  307. passy->view_dispatcher, PassyCustomEventReaderError);
  308. break;
  309. }
  310. size_t body_size = asn1_length(header + 1);
  311. uint8_t body_offset = sizeof(header);
  312. bit_buffer_append_bytes(passy_reader->DG1, header, sizeof(header));
  313. do {
  314. view_dispatcher_send_custom_event(
  315. passy->view_dispatcher, PassyCustomEventReaderReading);
  316. uint8_t chunk[PASSY_READER_DG1_CHUNK_SIZE];
  317. uint8_t Le = MIN(sizeof(chunk), (size_t)(body_size - body_offset));
  318. ret = passy_reader_read_binary(passy_reader, body_offset, Le, chunk);
  319. if(ret != NfcCommandContinue) {
  320. view_dispatcher_send_custom_event(
  321. passy->view_dispatcher, PassyCustomEventReaderError);
  322. break;
  323. }
  324. bit_buffer_append_bytes(passy_reader->DG1, chunk, sizeof(chunk));
  325. body_offset += sizeof(chunk);
  326. } while(body_offset < body_size);
  327. passy_log_bitbuffer(TAG, "DG1", passy_reader->DG1);
  328. } else if(passy->read_type == PassyReadDG2) {
  329. ret = passy_reader_select_file(passy_reader, 0x0102);
  330. if(ret != NfcCommandContinue) {
  331. view_dispatcher_send_custom_event(
  332. passy->view_dispatcher, PassyCustomEventReaderError);
  333. break;
  334. }
  335. uint8_t header[4];
  336. ret = passy_reader_read_binary(passy_reader, 0x00, 0x04, header);
  337. if(ret != NfcCommandContinue) {
  338. view_dispatcher_send_custom_event(
  339. passy->view_dispatcher, PassyCustomEventReaderError);
  340. break;
  341. }
  342. view_dispatcher_send_custom_event(
  343. passy->view_dispatcher, PassyCustomEventReaderReading);
  344. size_t body_size = asn1_length(header + 1);
  345. FURI_LOG_I(TAG, "DG2 length: %d", body_size);
  346. FuriString* path = furi_string_alloc();
  347. furi_string_printf(path, "%s/%s%s", STORAGE_APP_DATA_PATH_PREFIX, "DG2", ".bin");
  348. Storage* storage = furi_record_open(RECORD_STORAGE);
  349. Stream* stream = file_stream_alloc(storage);
  350. file_stream_open(stream, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_ALWAYS);
  351. uint8_t chunk[PASSY_READER_DG2_CHUNK_SIZE];
  352. size_t body_offset = sizeof(header);
  353. size_t chunk_count = (body_size + sizeof(chunk) - 1) / sizeof(chunk);
  354. size_t i = 0;
  355. do {
  356. memset(chunk, 0, sizeof(chunk));
  357. uint8_t Le = MIN(sizeof(chunk), (size_t)(body_size - body_offset));
  358. ret = passy_reader_read_binary(passy_reader, body_offset, Le, chunk);
  359. if(ret != NfcCommandContinue) {
  360. view_dispatcher_send_custom_event(
  361. passy->view_dispatcher, PassyCustomEventReaderError);
  362. break;
  363. }
  364. body_offset += sizeof(chunk);
  365. FURI_LOG_D(TAG, "chunk %02x/%02x offset %d", ++i, chunk_count, body_offset);
  366. // passy_log_buffer(TAG, "chunk", chunk, sizeof(chunk));
  367. stream_write(stream, chunk, Le);
  368. } while(body_offset < body_size);
  369. file_stream_close(stream);
  370. furi_record_close(RECORD_STORAGE);
  371. furi_string_free(path);
  372. }
  373. // Everything done
  374. ret = NfcCommandStop;
  375. view_dispatcher_send_custom_event(passy->view_dispatcher, PassyCustomEventReaderSuccess);
  376. } while(false);
  377. return ret;
  378. }
  379. NfcCommand passy_reader_poller_callback(NfcGenericEvent event, void* context) {
  380. furi_assert(event.protocol == NfcProtocolIso14443_4b);
  381. Passy* passy = context;
  382. NfcCommand ret = NfcCommandContinue;
  383. const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data;
  384. Iso14443_4bPoller* iso14443_4b_poller = event.instance;
  385. FURI_LOG_D(TAG, "iso14443_4b_event->type %i", iso14443_4b_event->type);
  386. PassyReader* passy_reader = passy_reader_alloc(passy, iso14443_4b_poller);
  387. if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) {
  388. view_dispatcher_send_custom_event(passy->view_dispatcher, PassyCustomEventReaderDetected);
  389. nfc_device_set_data(
  390. passy->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(passy->poller));
  391. ret = passy_reader_state_machine(passy, passy_reader);
  392. furi_thread_set_current_priority(FuriThreadPriorityLowest);
  393. } else if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeError) {
  394. Iso14443_4bPollerEventData* data = iso14443_4b_event->data;
  395. Iso14443_4bError error = data->error;
  396. FURI_LOG_W(TAG, "Iso14443_4bError %i", error);
  397. switch(error) {
  398. case Iso14443_4bErrorNone:
  399. break;
  400. case Iso14443_4bErrorNotPresent:
  401. break;
  402. case Iso14443_4bErrorProtocol:
  403. ret = NfcCommandStop;
  404. break;
  405. case Iso14443_4bErrorTimeout:
  406. break;
  407. }
  408. }
  409. passy_reader_free(passy_reader);
  410. return ret;
  411. }