Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moscow social card parser #3464

Merged
merged 57 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
380bfd3
Updated troyka layout (full version)
assasinfil Jan 23, 2024
fbe2387
Changed to furi func
assasinfil Jan 23, 2024
4254766
Small refactor
assasinfil Jan 24, 2024
39d4925
Merge branch 'dev' into troyka
assasinfil Feb 15, 2024
d13c862
Merge branch 'flipperdevices:dev' into troyka
assasinfil Feb 15, 2024
de991f3
Bitlib refactor
assasinfil Feb 15, 2024
029d86d
Moved to API
assasinfil Feb 15, 2024
2be5da0
Rollback troyka parser
assasinfil Feb 15, 2024
3f16155
Fix functions
assasinfil Feb 15, 2024
751ac9d
Added MSK Social card parser
assasinfil Feb 15, 2024
2e304ab
Merge branch 'flipperdevices:dev' into troyka
assasinfil Feb 17, 2024
8c25e30
Merge branch 'flipperdevices:dev' into troyka
assasinfil Feb 18, 2024
3a95260
Merge branch 'flipperdevices:dev' into msk-social
assasinfil Feb 18, 2024
ba19f56
Parser func refactor start
assasinfil Feb 18, 2024
4689a0f
Merge branch 'troyka' of https://github.com/assasinfil/flipperzero-fi…
assasinfil Feb 18, 2024
dd9c7a6
Layout E3 refactored
assasinfil Feb 18, 2024
a31ba57
Layout E4 refactored
assasinfil Feb 18, 2024
9aa93cf
Layout 6 refactored
assasinfil Feb 19, 2024
c5b1a0f
Layout E5 refactored
assasinfil Feb 19, 2024
2f5e654
Layout 2 refactored
assasinfil Feb 19, 2024
4d52d9f
Layout E5 fix
assasinfil Feb 19, 2024
3a727ec
Layout E6 refactored, valid_date need fix
assasinfil Feb 19, 2024
9408f91
Layout E6 fix
assasinfil Feb 19, 2024
40d2aa4
Layout FCB refactored
assasinfil Feb 19, 2024
ba60283
Layout F0B refactored
assasinfil Feb 19, 2024
beb6d83
Layout 8 refactored
assasinfil Feb 19, 2024
92ad9cb
Layout A refactored
assasinfil Feb 19, 2024
218bf03
Layout C refactored
assasinfil Feb 19, 2024
b61063f
Layout D refactored
assasinfil Feb 19, 2024
e4ef65f
Layout E1 refactored
assasinfil Feb 19, 2024
6e31a89
Layout E2 refactored
assasinfil Feb 19, 2024
3c8d54a
Fixes
assasinfil Feb 19, 2024
c15825e
Old code cleanup
assasinfil Feb 19, 2024
5c0db22
Memory cleanup
assasinfil Feb 19, 2024
54aa354
Merge branch 'troyka' into msk-social
assasinfil Feb 19, 2024
bdf466e
Unused imports cleanup
assasinfil Feb 19, 2024
35b074a
Keys struct refactor
assasinfil Feb 19, 2024
aa28ce4
Merge branch 'troyka' into msk-social
assasinfil Feb 19, 2024
395884e
Keys struct refactor
assasinfil Feb 19, 2024
9a4b421
Layout E1 fix
assasinfil Feb 19, 2024
854eedb
Merge branch 'troyka' into msk-social
assasinfil Feb 19, 2024
9e7ba3e
Merge branch 'dev' into troyka
gornekich Mar 1, 2024
8534b39
Merge branch 'troyka' into msk-social
assasinfil Mar 2, 2024
e615b52
Added debug info for layout and department
assasinfil Mar 4, 2024
ed26684
Merge branch 'dev' into troyka
gornekich Mar 4, 2024
973b155
Merge branch 'dev' into troyka
skotopes Mar 5, 2024
2f6ea28
Fix PVS warnings
skotopes Mar 5, 2024
7797276
Fix more PVS warnings
skotopes Mar 5, 2024
280b50b
Merge branch 'troyka' into msk-social
assasinfil Apr 19, 2024
0c2e842
Merge branch 'dev' into msk-social
assasinfil Apr 19, 2024
984c1bd
Merge branch 'dev' into msk-social
assasinfil Sep 17, 2024
4b9a3f4
Fix social card parse validation
assasinfil Sep 18, 2024
f4c3f07
Added card number validation
assasinfil Sep 20, 2024
a68bd33
Added transport data ui improvements from Astrrra's troyka render func.
assasinfil Sep 21, 2024
20b35f7
Merge branch 'flipperdevices:dev' into msk-social
assasinfil Oct 3, 2024
d598516
fbt format
assasinfil Oct 3, 2024
f8f7945
Merge branch 'dev' into msk-social
skotopes Oct 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions applications/main/nfc/api/mosgortrans/mosgortrans_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

#define TAG "Mosgortrans"

void render_section_header(
FuriString* str,
const char* name,
uint8_t prefix_separator_cnt,
uint8_t suffix_separator_cnt) {
for(uint8_t i = 0; i < prefix_separator_cnt; i++) {
furi_string_cat_printf(str, ":");
}
furi_string_cat_printf(str, "[ %s ]", name);
for(uint8_t i = 0; i < suffix_separator_cnt; i++) {
furi_string_cat_printf(str, ":");
}
}

void from_days_to_datetime(uint32_t days, DateTime* datetime, uint16_t start_year) {
uint32_t timestamp = days * 24 * 60 * 60;
DateTime start_datetime = {0};
Expand Down
5 changes: 5 additions & 0 deletions applications/main/nfc/api/mosgortrans/mosgortrans_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
extern "C" {
#endif

void render_section_header(
FuriString* str,
const char* name,
uint8_t prefix_separator_cnt,
uint8_t suffix_separator_cnt);
bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result);

#ifdef __cplusplus
Expand Down
9 changes: 8 additions & 1 deletion applications/main/nfc/api/nfc_app_api_table_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@ static constexpr auto nfc_app_api_table = sort(create_array_t<sym_entry>(
API_METHOD(
mosgortrans_parse_transport_block,
bool,
(const MfClassicBlock* block, FuriString* result))));
(const MfClassicBlock* block, FuriString* result)),
API_METHOD(
render_section_header,
void,
(FuriString * str,
const char* name,
uint8_t prefix_separator_cnt,
uint8_t suffix_separator_cnt))));
9 changes: 9 additions & 0 deletions applications/main/nfc/application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ App(
sources=["plugins/supported_cards/troika.c"],
)

App(
appid="social_moscow_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="social_moscow_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/social_moscow.c"],
)

App(
appid="washcity_parser",
apptype=FlipperAppType.PLUGIN,
Expand Down
301 changes: 301 additions & 0 deletions applications/main/nfc/plugins/supported_cards/social_moscow.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
#include "nfc_supported_card_plugin.h"
#include <core/check.h>

#include <flipper_application/flipper_application.h>

#include <nfc/nfc_device.h>
#include <bit_lib/bit_lib.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include "../../api/mosgortrans/mosgortrans_util.h"
#include "furi_hal_rtc.h"

#define TAG "Social_Moscow"

typedef struct {
uint64_t a;
uint64_t b;
} MfClassicKeyPair;

typedef struct {
const MfClassicKeyPair* keys;
uint32_t data_sector;
} SocialMoscowCardConfig;

static const MfClassicKeyPair social_moscow_1k_keys[] = {
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025},
{.a = 0x2735fc181807, .b = 0xbf23a53c1f63},
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368},
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
{.a = 0x73068f118c13, .b = 0x2b7f3253fac5},
{.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057},
{.a = 0x3a4bba8adaf0, .b = 0x67362d90f973},
{.a = 0x8765b17968a2, .b = 0x6202a38f69e2},
{.a = 0x40ead80721ce, .b = 0x100533b89331},
{.a = 0x0db5e6523f7c, .b = 0x653a87594079},
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368},
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}};

static const MfClassicKeyPair social_moscow_4k_keys[] = {
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //1
{.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, //2
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //3
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //4
{.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, //5
{.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, //6
{.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, //7
{.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, //8
{.a = 0x40ead80721ce, .b = 0x100533b89331}, //9
{.a = 0x0db5e6523f7c, .b = 0x653a87594079}, //10
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //11
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //12
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //13
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //14
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //15
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //16
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //17
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //18
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //19
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //20
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //21
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //22
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //23
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //24
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //25
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //26
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //27
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //28
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //29
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //30
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //31
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //32
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //33
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //34
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //35
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //36
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //37
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //38
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //39
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //40
};

static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) {
bool success = true;
if(type == MfClassicType1k) {
config->data_sector = 15;
config->keys = social_moscow_1k_keys;
} else if(type == MfClassicType4k) {
config->data_sector = 15;
assasinfil marked this conversation as resolved.
Show resolved Hide resolved
config->keys = social_moscow_4k_keys;
} else {
success = false;
}

return success;
}

static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) {
bool verified = false;

do {
SocialMoscowCardConfig cfg = {};
if(!social_moscow_get_card_config(&cfg, type)) break;

const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector);

MfClassicKey key = {0};
bit_lib_num_to_bytes_be(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data);

MfClassicAuthContext auth_context;
MfClassicError error =
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
if(error != MfClassicErrorNone) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
break;
}
FURI_LOG_D(TAG, "Verify success!");
verified = true;
} while(false);

return verified;
}

static bool social_moscow_verify(Nfc* nfc) {
return social_moscow_verify_type(nfc, MfClassicType1k) ||
social_moscow_verify_type(nfc, MfClassicType4k);
}

static bool social_moscow_read(Nfc* nfc, NfcDevice* device) {
furi_assert(nfc);
furi_assert(device);

bool is_read = false;

MfClassicData* data = mf_classic_alloc();
nfc_device_copy_data(device, NfcProtocolMfClassic, data);

do {
MfClassicType type = MfClassicType4k;
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
if(error != MfClassicErrorNone) break;

data->type = type;
SocialMoscowCardConfig cfg = {};
if(!social_moscow_get_card_config(&cfg, data->type)) break;

MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
FURI_BIT_SET(keys.key_b_mask, i);
}

error = mf_classic_poller_sync_read(nfc, &keys, data);
if(error == MfClassicErrorNotPresent) {
FURI_LOG_W(TAG, "Failed to read data");
break;
}

nfc_device_set_data(device, NfcProtocolMfClassic, data);

is_read = (error == MfClassicErrorNone);
} while(false);

mf_classic_free(data);

return is_read;
}

static uint8_t calculate_luhn(uint64_t number) {
// https://en.wikipedia.org/wiki/Luhn_algorithm
// Drop existing check digit to form payload
uint64_t payload = number / 10;
int sum = 0;
int position = 0;

while(payload > 0) {
int digit = payload % 10;
if(position % 2 == 0) {
digit *= 2;
}
if(digit > 9) {
digit = (digit / 10) + (digit % 10);
}
sum += digit;
payload /= 10;
position++;
}

return (10 - (sum % 10)) % 10;
}

static uint64_t hex_num(uint64_t hex) {
uint64_t result = 0;
for(uint8_t i = 0; i < 8; ++i) {
uint8_t half_byte = hex & 0x0F;
uint64_t num = 0;
for(uint8_t j = 0; j < 4; ++j) {
num += (half_byte & 0x1) * (1 << j);
half_byte = half_byte >> 1;
}
result += num * pow(10, i);
hex = hex >> 4;
}
return result;
}

static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);

const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);

bool parsed = false;

do {
// Verify card type
SocialMoscowCardConfig cfg = {};
if(!social_moscow_get_card_config(&cfg, data->type)) break;

// Verify key
const MfClassicSectorTrailer* sec_tr =
mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector);

const uint64_t key_a =
bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
const uint64_t key_b =
bit_lib_bytes_to_num_be(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data));
if((key_a != cfg.keys[cfg.data_sector].a) || (key_b != cfg.keys[cfg.data_sector].b)) break;

uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24);
uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8);
uint64_t card_number = bit_lib_get_bits_64(data->block[60].data, 40, 40);
uint8_t card_control = bit_lib_get_bits(data->block[60].data, 80, 4);
uint64_t omc_number = bit_lib_get_bits_64(data->block[21].data, 8, 64);
uint8_t year = data->block[60].data[11];
uint8_t month = data->block[60].data[12];

uint64_t number = hex_num(card_control) + hex_num(card_number) * 10 +
hex_num(card_region) * 10 * 10000000000 +
hex_num(card_code) * 10 * 10000000000 * 100;

uint8_t luhn = calculate_luhn(number);
if(luhn != card_control) break;

FuriString* metro_result = furi_string_alloc();
FuriString* ground_result = furi_string_alloc();
bool is_metro_data_present =
mosgortrans_parse_transport_block(&data->block[4], metro_result);
bool is_ground_data_present =
mosgortrans_parse_transport_block(&data->block[16], ground_result);
furi_string_cat_printf(
parsed_data,
"\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n",
card_code,
card_region,
card_number,
card_control,
omc_number,
month,
year,
data->block[60].data[13],
data->block[60].data[14]);
if(is_metro_data_present && !furi_string_empty(metro_result)) {
render_section_header(parsed_data, "Metro", 22, 21);
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result));
}
if(is_ground_data_present && !furi_string_empty(ground_result)) {
render_section_header(parsed_data, "Ground", 21, 20);
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result));
}
furi_string_free(ground_result);
furi_string_free(metro_result);
parsed = true;
} while(false);

return parsed;
}

/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin social_moscow_plugin = {
.protocol = NfcProtocolMfClassic,
.verify = social_moscow_verify,
.read = social_moscow_read,
.parse = social_moscow_parse,
};

/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor social_moscow_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &social_moscow_plugin,
};

/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* social_moscow_plugin_ep() {
return &social_moscow_plugin_descriptor;
}
Loading