NFC: Add parser for CSC Service Works Reloadable Cash Card (#137)

* csc parser

* Added verification through keys. Recognize new cards.

* Correct typo, add comments

* Updated output word choice

* Added verify key in csc_parse to avoid false positive

* Format

* updated verification process

- Deleted key-based verification
- Added memory based checksum and backup block checks to ensure better verification performance

* Formatting and More Verify Steps

Added card type checks and verify for csc specific memory format
Formatted the code (indentation etc.)

* Update csc.c

* Test cleanup parser a bit

* Update changelog

---------

Co-authored-by: Willy-JL <49810075+Willy-JL@users.noreply.github.com>
This commit is contained in:
Zinong Li
2024-07-10 06:43:38 -04:00
committed by GitHub
parent b7bd0b1d14
commit 79d8b12c5e
3 changed files with 159 additions and 0 deletions

View File

@@ -10,6 +10,7 @@
- UL: Add manually Hormann EcoStar, also Sommer FM238 option when default won't work (by @xMasterX)
- OFW: Princeton protocol add custom guard time (by @Skorpionm & @xMasterX)
- NFC:
- Add parser for CSC Service Works Reloadable Cash Card (by @zinongli)
- OFW: Mifare Plus detection support (by @Astrrra)
- OFW: Felica emulation (by @RebornedBrain)
- OFW: Write to Ultralight cards is now possible (by @RebornedBrain & @gornekich)

View File

@@ -281,6 +281,15 @@ App(
sources=["plugins/supported_cards/sonicare.c"],
)
App(
appid="csc_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="csc_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/csc.c"],
)
App(
appid="nfc_cli",
targets=["f7"],

View File

@@ -0,0 +1,149 @@
/*
* Parser for CSC Service Works Reloadable Cash Card (US)
* Date created 2024/5/26
* Zinong Li
* Discord @torron0483
* Github @zinongli
*/
#include "nfc_supported_card_plugin.h"
#include <flipper_application.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include <bit_lib.h>
#include <furi/core/string.h>
#include <nfc/nfc.h>
#include <nfc/nfc_device.h>
#define TAG "CSC"
bool csc_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
bool parsed = false;
do {
if(data->type != MfClassicType1k) break; // Check card type
// Verify memory format (checksum is later)
const uint8_t refill_block_num = 2;
const uint8_t current_balance_block_num = 4;
const uint8_t current_balance_copy_block_num = 8;
const uint8_t* current_balance_block_start_ptr =
&data->block[current_balance_block_num].data[0];
const uint8_t* current_balance_copy_block_start_ptr =
&data->block[current_balance_copy_block_num].data[0];
uint32_t current_balance_and_times =
bit_lib_bytes_to_num_le(current_balance_block_start_ptr, 4);
uint32_t current_balance_and_times_copy =
bit_lib_bytes_to_num_le(current_balance_copy_block_start_ptr, 4);
// Failed verification if balance != backup
if(current_balance_and_times != current_balance_and_times_copy) {
FURI_LOG_D(TAG, "Backup verification failed");
break;
}
// Even if balance = 0, e.g. new card, refilled times can't be zero
if(current_balance_and_times == 0 || current_balance_and_times_copy == 0) {
FURI_LOG_D(TAG, "Value bytes empty");
break;
}
// Parse data
const uint8_t card_lives_block_num = 9;
const uint8_t refill_sign_block_num = 13;
const uint8_t* refilled_balance_block_start_ptr = &data->block[refill_block_num].data[9];
const uint8_t* refill_times_block_start_ptr = &data->block[refill_block_num].data[5];
const uint8_t* card_lives_block_start_ptr = &data->block[card_lives_block_num].data[0];
const uint8_t* refill_sign_block_start_ptr = &data->block[refill_sign_block_num].data[0];
uint32_t refilled_balance = bit_lib_bytes_to_num_le(refilled_balance_block_start_ptr, 2);
uint32_t refilled_balance_dollar = refilled_balance / 100;
uint8_t refilled_balance_cent = refilled_balance % 100;
uint32_t current_balance = bit_lib_bytes_to_num_le(current_balance_block_start_ptr, 2);
uint32_t current_balance_dollar = current_balance / 100;
uint8_t current_balance_cent = current_balance % 100;
// How many times it can still be used
uint32_t card_lives = bit_lib_bytes_to_num_le(card_lives_block_start_ptr, 2);
uint32_t refill_times = bit_lib_bytes_to_num_le(refill_times_block_start_ptr, 2);
// This is zero when you buy the card. but after refilling it, the refilling machine will leave a non-zero signature here
uint64_t refill_sign = bit_lib_bytes_to_num_le(refill_sign_block_start_ptr, 8);
size_t uid_len = 0;
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
uint32_t card_uid = bit_lib_bytes_to_num_le(uid, 4);
// Last byte of refill block is checksum
const uint8_t* checksum_block = data->block[refill_block_num].data;
uint8_t xor_result = 0;
for(size_t i = 0; i < 16; ++i) {
xor_result ^= checksum_block[i];
}
if(refill_sign == 0 && refill_times == 1) {
// New cards don't comply to checksum but refill time should be once
furi_string_printf(
parsed_data,
"\e#CSC Service Works\n"
"UID: %lu\n"
"New Card\n"
"Card Value: %lu.%02u USD\n"
"Card Usages Left: %lu",
card_uid,
refilled_balance_dollar,
refilled_balance_cent,
card_lives);
} else {
if(xor_result != 0) {
FURI_LOG_D(TAG, "Checksum failed");
break;
}
furi_string_printf(
parsed_data,
"\e#CSC Service Works\n"
"UID: %lu\n"
"Balance: %lu.%02u USD\n"
"Last Top-up: %lu.%02u USD\n"
"Top-up Count: %lu\n"
"Card Usages Left: %lu",
card_uid,
current_balance_dollar,
current_balance_cent,
refilled_balance_dollar,
refilled_balance_cent,
refill_times,
card_lives);
}
parsed = true;
} while(false);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin csc_plugin = {
.protocol = NfcProtocolMfClassic,
.verify = NULL,
.read = NULL,
.parse = csc_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor csc_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &csc_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* csc_plugin_ep(void) {
return &csc_plugin_descriptor;
}