diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h index d31857b09f..fcaa88111f 100644 --- a/applications/main/nfc/api/nfc_app_api_table_i.h +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -1,5 +1,6 @@ #include "gallagher/gallagher_util.h" #include "mosgortrans/mosgortrans_util.h" +#include "saflok/saflok_util.h" #include "../nfc_app_i.h" #include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" #include "../helpers/protocol_support/nfc_protocol_support_unlock_helper.h" @@ -41,4 +42,16 @@ static constexpr auto nfc_app_api_table = sort(create_array_t( bool, (NfcApp * instance, SceneManagerEvent event)), API_METHOD(nfc_unlock_helper_setup_from_state, void, (NfcApp * instance)), - API_METHOD(nfc_unlock_helper_card_detected_handler, void, (NfcApp * instance)))); + API_METHOD(nfc_unlock_helper_card_detected_handler, void, (NfcApp * instance)), + API_METHOD(saflok_calculate_checksum, uint8_t, (uint8_t data[BASIC_ACCESS_BYTE_NUM])), + API_METHOD(saflok_generate_key, void, (const uint8_t* uid, uint8_t* key)), + API_METHOD( + saflok_decrypt_card, + void, + (uint8_t strCard[BASIC_ACCESS_BYTE_NUM], + int length, + uint8_t decryptedCard[BASIC_ACCESS_BYTE_NUM])), + API_METHOD( + saflok_encrypt_card, + void, + (unsigned char* keyCard, int length, unsigned char* encryptedCard)))); diff --git a/applications/main/nfc/api/saflok/saflok_util.c b/applications/main/nfc/api/saflok/saflok_util.c new file mode 100644 index 0000000000..6e4e26ac21 --- /dev/null +++ b/applications/main/nfc/api/saflok/saflok_util.c @@ -0,0 +1,157 @@ +// KDF from: https://gitee.com/jadenwu/Saflok_KDF/blob/master/saflok.c +// KDF published and reverse engineered by Jaden Wu + +// Decryption and parsing from: https://gitee.com/wangshuoyue/unsaflok +// Decryption algorithm and parsing published by Shuoyue Wang + +// Encryption from: https://github.com/RfidResearchGroup/proxmark3/blob/master/client/src/cmdhfsaflok.c + +#include "saflok_util.h" + +#include + +#include + +void saflok_generate_key(const uint8_t* uid, uint8_t* key) { + static const uint8_t magic_table[192] = { + 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xF0, 0x57, 0xB3, 0x9E, 0xE3, 0xD8, 0x00, 0x00, 0xAA, + 0x00, 0x00, 0x00, 0x96, 0x9D, 0x95, 0x4A, 0xC1, 0x57, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, + 0x8F, 0x43, 0x58, 0x0D, 0x2C, 0x9D, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xFF, 0xCC, 0xE0, + 0x05, 0x0C, 0x43, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x34, 0x1B, 0x15, 0xA6, 0x90, 0xCC, + 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x89, 0x58, 0x56, 0x12, 0xE7, 0x1B, 0x00, 0x00, 0xAA, + 0x00, 0x00, 0x00, 0xBB, 0x74, 0xB0, 0x95, 0x36, 0x58, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, + 0xFB, 0x97, 0xF8, 0x4B, 0x5B, 0x74, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xC9, 0xD1, 0x88, + 0x35, 0x9F, 0x92, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x8F, 0x92, 0xE9, 0x7F, 0x58, 0x97, + 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x16, 0x6C, 0xA2, 0xB0, 0x9F, 0xD1, 0x00, 0x00, 0xAA, + 0x00, 0x00, 0x00, 0x27, 0xDD, 0x93, 0x10, 0x1C, 0x6C, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, + 0xDA, 0x3E, 0x3F, 0xD6, 0x49, 0xDD, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x58, 0xDD, 0xED, + 0x07, 0x8E, 0x3E, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x5C, 0xD0, 0x05, 0xCF, 0xD9, 0x07, + 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x11, 0x8D, 0xD0, 0x01, 0x87, 0xD0}; + + uint8_t magic_byte = (uid[3] >> 4) + (uid[2] >> 4) + (uid[0] & 0x0F); + uint8_t magickal_index = (magic_byte & 0x0F) * 12 + 11; + + uint8_t temp_key[6] = {magic_byte, uid[0], uid[1], uid[2], uid[3], magic_byte}; + uint8_t carry_sum = 0; + + for(int i = 6 - 1; i >= 0; i--, magickal_index--) { + uint16_t keysum = temp_key[i] + magic_table[magickal_index] + carry_sum; + temp_key[i] = (keysum & 0xFF); + carry_sum = keysum >> 8; + } + + memcpy(key, temp_key, 6); +} + +// Lookup table +unsigned char c_aEncode[256] = { + 236, 116, 192, 99, 86, 153, 105, 100, 159, 23, 38, 198, 240, 1, 16, 77, 202, 82, 138, + 75, 122, 175, 173, 32, 115, 162, 15, 194, 80, 120, 54, 68, 25, 30, 114, 210, 50, 183, + 107, 248, 5, 174, 199, 28, 85, 113, 89, 19, 17, 73, 250, 252, 127, 43, 52, 102, 69, + 165, 185, 21, 169, 163, 134, 150, 219, 45, 218, 208, 33, 84, 189, 227, 131, 141, 110, 155, + 83, 149, 4, 228, 42, 112, 39, 94, 35, 133, 135, 36, 209, 237, 34, 27, 214, 98, 118, + 67, 48, 193, 66, 132, 91, 253, 95, 40, 254, 58, 20, 55, 176, 184, 26, 61, 171, 72, + 251, 152, 3, 166, 119, 201, 11, 117, 97, 8, 241, 245, 217, 121, 101, 172, 229, 164, 223, + 191, 235, 10, 204, 249, 125, 195, 136, 13, 142, 232, 220, 247, 143, 156, 47, 109, 161, 65, + 9, 188, 92, 60, 57, 144, 124, 197, 46, 212, 51, 78, 206, 213, 88, 79, 200, 216, 31, + 130, 22, 62, 215, 255, 190, 146, 157, 196, 211, 14, 29, 181, 93, 24, 7, 126, 106, 243, + 37, 128, 108, 203, 70, 140, 246, 231, 242, 177, 187, 41, 145, 158, 205, 233, 148, 224, 170, + 137, 221, 234, 230, 81, 168, 71, 63, 2, 59, 87, 96, 12, 207, 238, 154, 160, 179, 123, + 225, 147, 186, 178, 182, 222, 0, 226, 167, 139, 76, 53, 74, 111, 239, 18, 129, 44, 180, + 56, 90, 244, 151, 64, 104, 6, 49, 103}; + +void saflok_encrypt_card(unsigned char* keyCard, int length, unsigned char* encryptedCard) { + int b = 0; + memcpy(encryptedCard, keyCard, length); + for(int i = 0; i < length; i++) { + int b2 = encryptedCard[i]; + int num2 = i; + for(int j = 0; j < 8; j++) { + num2 += 1; + if(num2 >= length) { + num2 -= length; + } + int b3 = encryptedCard[num2]; + int b4 = b2 & 1; + b2 = (b2 >> 1) | (b << 7); + b = b3 & 1; + b3 = (b3 >> 1) | (b4 << 7); + encryptedCard[num2] = b3; + } + encryptedCard[i] = b2; + } + if(length == 17) { + int b2 = encryptedCard[10]; + b2 |= b; + encryptedCard[10] = b2; + } + for(int i = 0; i < length; i++) { + int j = encryptedCard[i] + (i + 1); + if(j > 255) { + j -= 256; + } + encryptedCard[i] = c_aEncode[j]; + } +} + +// Lookup table +static const uint8_t c_aDecode[256] = { + 0xEA, 0x0D, 0xD9, 0x74, 0x4E, 0x28, 0xFD, 0xBA, 0x7B, 0x98, 0x87, 0x78, 0xDD, 0x8D, 0xB5, + 0x1A, 0x0E, 0x30, 0xF3, 0x2F, 0x6A, 0x3B, 0xAC, 0x09, 0xB9, 0x20, 0x6E, 0x5B, 0x2B, 0xB6, + 0x21, 0xAA, 0x17, 0x44, 0x5A, 0x54, 0x57, 0xBE, 0x0A, 0x52, 0x67, 0xC9, 0x50, 0x35, 0xF5, + 0x41, 0xA0, 0x94, 0x60, 0xFE, 0x24, 0xA2, 0x36, 0xEF, 0x1E, 0x6B, 0xF7, 0x9C, 0x69, 0xDA, + 0x9B, 0x6F, 0xAD, 0xD8, 0xFB, 0x97, 0x62, 0x5F, 0x1F, 0x38, 0xC2, 0xD7, 0x71, 0x31, 0xF0, + 0x13, 0xEE, 0x0F, 0xA3, 0xA7, 0x1C, 0xD5, 0x11, 0x4C, 0x45, 0x2C, 0x04, 0xDB, 0xA6, 0x2E, + 0xF8, 0x64, 0x9A, 0xB8, 0x53, 0x66, 0xDC, 0x7A, 0x5D, 0x03, 0x07, 0x80, 0x37, 0xFF, 0xFC, + 0x06, 0xBC, 0x26, 0xC0, 0x95, 0x4A, 0xF1, 0x51, 0x2D, 0x22, 0x18, 0x01, 0x79, 0x5E, 0x76, + 0x1D, 0x7F, 0x14, 0xE3, 0x9E, 0x8A, 0xBB, 0x34, 0xBF, 0xF4, 0xAB, 0x48, 0x63, 0x55, 0x3E, + 0x56, 0x8C, 0xD1, 0x12, 0xED, 0xC3, 0x49, 0x8E, 0x92, 0x9D, 0xCA, 0xB1, 0xE5, 0xCE, 0x4D, + 0x3F, 0xFA, 0x73, 0x05, 0xE0, 0x4B, 0x93, 0xB2, 0xCB, 0x08, 0xE1, 0x96, 0x19, 0x3D, 0x83, + 0x39, 0x75, 0xEC, 0xD6, 0x3C, 0xD0, 0x70, 0x81, 0x16, 0x29, 0x15, 0x6C, 0xC7, 0xE7, 0xE2, + 0xF6, 0xB7, 0xE8, 0x25, 0x6D, 0x3A, 0xE6, 0xC8, 0x99, 0x46, 0xB0, 0x85, 0x02, 0x61, 0x1B, + 0x8B, 0xB3, 0x9F, 0x0B, 0x2A, 0xA8, 0x77, 0x10, 0xC1, 0x88, 0xCC, 0xA4, 0xDE, 0x43, 0x58, + 0x23, 0xB4, 0xA1, 0xA5, 0x5C, 0xAE, 0xA9, 0x7E, 0x42, 0x40, 0x90, 0xD2, 0xE9, 0x84, 0xCF, + 0xE4, 0xEB, 0x47, 0x4F, 0x82, 0xD4, 0xC5, 0x8F, 0xCD, 0xD3, 0x86, 0x00, 0x59, 0xDF, 0xF2, + 0x0C, 0x7C, 0xC6, 0xBD, 0xF9, 0x7D, 0xC4, 0x91, 0x27, 0x89, 0x32, 0x72, 0x33, 0x65, 0x68, + 0xAF}; + +void saflok_decrypt_card( + uint8_t strCard[BASIC_ACCESS_BYTE_NUM], + int length, + uint8_t decryptedCard[BASIC_ACCESS_BYTE_NUM]) { + int i, num, num2, num3, num4, b = 0, b2 = 0; + for(i = 0; i < length; i++) { + num = c_aDecode[strCard[i]] - (i + 1); + if(num < 0) num += 256; + decryptedCard[i] = num; + } + + if(length == 17) { + b = decryptedCard[10]; + b2 = b & 1; + } + + for(num2 = length; num2 > 0; num2--) { + b = decryptedCard[num2 - 1]; + for(num3 = 8; num3 > 0; num3--) { + num4 = num2 + num3; + if(num4 > length) num4 -= length; + int b3 = decryptedCard[num4 - 1]; + int b4 = (b3 & 0x80) >> 7; + b3 = ((b3 << 1) & 0xFF) | b2; + b2 = (b & 0x80) >> 7; + b = ((b << 1) & 0xFF) | b4; + decryptedCard[num4 - 1] = b3; + } + decryptedCard[num2 - 1] = b; + } +} + +uint8_t saflok_calculate_checksum(uint8_t data[BASIC_ACCESS_BYTE_NUM]) { + int sum = 0; + for(int i = 0; i < BASIC_ACCESS_BYTE_NUM - 1; i++) { + sum += data[i]; + } + sum = 255 - (sum & 0xFF); + return sum & 0xFF; +} diff --git a/applications/main/nfc/api/saflok/saflok_util.h b/applications/main/nfc/api/saflok/saflok_util.h new file mode 100644 index 0000000000..8ec4910076 --- /dev/null +++ b/applications/main/nfc/api/saflok/saflok_util.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#define BASIC_ACCESS_BYTE_NUM 17 +#define SAFLOK_YEAR_OFFSET 1980 + +#ifdef __cplusplus +extern "C" { +#endif + +uint8_t saflok_calculate_checksum(uint8_t data[BASIC_ACCESS_BYTE_NUM]); +void saflok_generate_key(const uint8_t* uid, uint8_t* key); +void saflok_decrypt_card( + uint8_t strCard[BASIC_ACCESS_BYTE_NUM], + int length, + uint8_t decryptedCard[BASIC_ACCESS_BYTE_NUM]); +void saflok_encrypt_card(unsigned char* keyCard, int length, unsigned char* encryptedCard); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index dc627a23cc..beb63307a5 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -374,8 +374,19 @@ App( ) App( - appid="saflok_parser", + appid="saflok_mfc_parser", apptype=FlipperAppType.PLUGIN, + cdefines=[("SL_PROTO", "SL_PROTO_MFC")], + entry_point="saflok_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/saflok.c"], +) + +App( + appid="saflok_ul_parser", + apptype=FlipperAppType.PLUGIN, + cdefines=[("SL_PROTO", "SL_PROTO_UL")], entry_point="saflok_plugin_ep", targets=["f7"], requires=["nfc"], diff --git a/applications/main/nfc/helpers/saflok.c b/applications/main/nfc/helpers/saflok.c new file mode 100644 index 0000000000..478192f103 --- /dev/null +++ b/applications/main/nfc/helpers/saflok.c @@ -0,0 +1,171 @@ +// Based on: https://github.com/RfidResearchGroup/proxmark3/blob/master/client/src/cmdhfsaflok.c +// Generation written by Aaron Tulino + +#include "saflok.h" + +#include + +#define ULC_DATA_START_PAGE 34 +#define ULC_3DES_START_PAGE 44 +#define ULC_DATA_NUM_PAGES 5 + +static void insert_bits(uint8_t* data, size_t start_bit, size_t num_bits, uint32_t value) { + for(size_t i = 0; i < num_bits; i++) { + size_t current_bit = start_bit + i; + size_t byte_index = current_bit / 8; + size_t bit_index = 7 - (current_bit % 8); + + uint32_t bit_value = (value >> (num_bits - 1 - i)) & 1U; + + data[byte_index] = (data[byte_index] & ~(1 << bit_index)) | (bit_value << bit_index); + } +} + +// Generates the 17-byte data buffer +void saflok_generate_data(NfcSaflokData* saflok_data, uint8_t* buffer) { + uint8_t basicAccess[BASIC_ACCESS_BYTE_NUM]; + memset(basicAccess, 0, BASIC_ACCESS_BYTE_NUM); + + insert_bits(basicAccess, 0, 4, saflok_data->card_level); + insert_bits(basicAccess, 4, 4, saflok_data->card_type); + insert_bits(basicAccess, 8, 8, saflok_data->card_id); + insert_bits(basicAccess, 16, 1, saflok_data->opening_key); + insert_bits(basicAccess, 17, 15, saflok_data->lock_id); + insert_bits(basicAccess, 32, 12, saflok_data->pass_number); + insert_bits(basicAccess, 44, 12, saflok_data->sequence_and_combination); + insert_bits(basicAccess, 56, 1, saflok_data->deadbolt_override); + insert_bits(basicAccess, 57, 7, saflok_data->restricted_days); + insert_bits(basicAccess, 116, 12, saflok_data->property_id); + + // Break creation date/time down and shove the bits in the right spots + uint16_t creation_year = saflok_data->creation.year - SAFLOK_YEAR_OFFSET; + basicAccess[14] |= creation_year & 0xF0; + basicAccess[11] = (creation_year << 4) & 0xF0; + basicAccess[11] |= saflok_data->creation.month & 0x0F; + + basicAccess[12] = (saflok_data->creation.day << 3) & 0xF8; + + basicAccess[12] |= (saflok_data->creation.hour >> 2) & 0x07; + basicAccess[13] = (saflok_data->creation.hour << 6) & 0xC0; + + basicAccess[13] |= saflok_data->creation.minute & 0x3F; + + // Expiration date is stored as a duration after creation + // Expiration time is stored as a time-of-day as-is + uint16_t expire_year = saflok_data->expire.year - saflok_data->creation.year; + int8_t expire_month = saflok_data->expire.month - saflok_data->creation.month; + int8_t expire_day = saflok_data->expire.day - saflok_data->creation.day; + + if(expire_month < 0) { + expire_month += 12; + expire_year -= 1; + } + + // Handle day rollover + // The 0th month is December, to make wrapping around easier + static const uint8_t days_in_month[] = {31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + while(true) { + uint16_t year = expire_year + saflok_data->creation.year; + uint8_t month = expire_month + saflok_data->creation.month; + if(month > 12) month -= 12; + + // minus 1 to get number of days in prior month + uint8_t max_days = days_in_month[month - 1]; + // Adjust for leap years + if(month == 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) { + max_days = 29; + } + if(expire_day >= 0) { + break; + } + + expire_day += max_days; + expire_month--; + if(expire_month < 0) { + expire_month += 12; + expire_year--; + } + } + basicAccess[8] = (expire_year << 4) & 0xF0; + basicAccess[8] |= expire_month & 0x0F; + + basicAccess[9] = (expire_day << 3) & 0xF8; + + basicAccess[9] |= (saflok_data->expire.hour >> 2) & 0x07; + basicAccess[10] = (saflok_data->expire.hour & 0x03) << 6; + + basicAccess[10] |= saflok_data->expire.minute & 0x3F; + + // Add checksum and encrypt + basicAccess[16] = saflok_calculate_checksum(basicAccess); + saflok_encrypt_card(basicAccess, BASIC_ACCESS_BYTE_NUM, buffer); +} + +void saflok_generate_mf_classic(NfcDevice* nfc_device, NfcSaflokData* saflok_data) { + MfClassicData* mfc_data = mf_classic_alloc(); + + uint8_t uid[ISO14443_3A_MAX_UID_SIZE]; + uid[0] = 0xEB; + uid[1] = 0xC7; + uid[2] = 0x04; + uid[3] = 0x4B; + mf_classic_set_uid(mfc_data, uid, 4); + + // Generate diversified key from UID + uint8_t key[6]; + saflok_generate_key(uid, key); + uint64_t diversified_key = bit_lib_bytes_to_num_be(key, 6); + + // Set up manufacturer block + mfc_data->iso14443_3a_data->uid_len = 4; + mfc_data->iso14443_3a_data->atqa[0] = 0x04; + mfc_data->iso14443_3a_data->atqa[1] = 0x00; + mfc_data->iso14443_3a_data->sak = 0x08; + mfc_data->type = MfClassicType1k; + mf_classic_set_block_read(mfc_data, 0, &mfc_data->block[0]); + + // Fill the remaining blocks + uint16_t block_num = mf_classic_get_total_block_num(MfClassicType1k); + for(uint16_t block = 1; block < block_num; block++) { + if(mf_classic_is_sector_trailer(block)) { + MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)mfc_data->block[block].data; + sec_tr->access_bits.data[0] = 0xFF; + sec_tr->access_bits.data[1] = 0x07; + sec_tr->access_bits.data[2] = 0x80; + sec_tr->access_bits.data[3] = 0x69; // Nice + + uint64_t sector_key = diversified_key; + if(mf_classic_get_sector_by_block(block) == 1) { + // Only for sector 1: use the Saflok standard key instead of the diversified key + sector_key = 0x2a2c13cc242a; + } + + mf_classic_set_block_read(mfc_data, block, &mfc_data->block[block]); + mf_classic_set_key_found( + mfc_data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeA, sector_key); + mf_classic_set_key_found( + mfc_data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeB, 0xFFFFFFFFFFFF); + + } else { + memset(&mfc_data->block[block].data, 0x00, MF_CLASSIC_BLOCK_SIZE); + } + + mf_classic_set_block_read(mfc_data, block, &mfc_data->block[block]); + + // This is the default log header for cards with no log data + // 00 00 00 00 00 00 00 00 00 00 00 C1 00 00 00 00 + if(block == 4) { + mfc_data->block[block].data[11] = 0xC1; + } + } + + uint8_t data[BASIC_ACCESS_BYTE_NUM]; + saflok_generate_data(saflok_data, data); + + // Saflok data is stored in block 1 and the first byte of block 2 + memcpy(mfc_data->block[1].data, data, 16); + mfc_data->block[2].data[0] = data[16]; + + nfc_device_set_data(nfc_device, NfcProtocolMfClassic, mfc_data); + mf_classic_free(mfc_data); +} diff --git a/applications/main/nfc/helpers/saflok.h b/applications/main/nfc/helpers/saflok.h new file mode 100644 index 0000000000..0cbc6e06de --- /dev/null +++ b/applications/main/nfc/helpers/saflok.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../nfc_app_i.h" +#include "../api/saflok/saflok_util.h" + +void saflok_generate_mf_classic(NfcDevice* nfc_device, NfcSaflokData* saflok_data); diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 1cff11abf1..0ca3c075ac 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -94,6 +94,13 @@ NfcApp* nfc_app_alloc(void) { view_dispatcher_add_view( instance->view_dispatcher, NfcViewPopup, popup_get_view(instance->popup)); + // Variable Item List + instance->variable_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, + NfcViewVariableItemList, + variable_item_list_get_view(instance->variable_item_list)); + // Loading instance->loading = loading_alloc(); view_dispatcher_add_view( @@ -109,6 +116,20 @@ NfcApp* nfc_app_alloc(void) { view_dispatcher_add_view( instance->view_dispatcher, NfcViewByteInput, byte_input_get_view(instance->byte_input)); + // Number Input + instance->number_input = number_input_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, + NfcViewNumberInput, + number_input_get_view(instance->number_input)); + + // Date Time Input + instance->date_time_input = date_time_input_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, + NfcViewDateTimeInput, + date_time_input_get_view(instance->date_time_input)); + // TextBox instance->text_box = text_box_alloc(); view_dispatcher_add_view( @@ -136,6 +157,8 @@ NfcApp* nfc_app_alloc(void) { instance->file_path = furi_string_alloc_set(NFC_APP_FOLDER); instance->file_name = furi_string_alloc(); + instance->nfc_saflok_data = malloc(sizeof(NfcSaflokData)); + return instance; } @@ -174,6 +197,10 @@ void nfc_app_free(NfcApp* instance) { view_dispatcher_remove_view(instance->view_dispatcher, NfcViewPopup); popup_free(instance->popup); + // Variable Item List + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewVariableItemList); + variable_item_list_free(instance->variable_item_list); + // Loading view_dispatcher_remove_view(instance->view_dispatcher, NfcViewLoading); loading_free(instance->loading); @@ -186,6 +213,14 @@ void nfc_app_free(NfcApp* instance) { view_dispatcher_remove_view(instance->view_dispatcher, NfcViewByteInput); byte_input_free(instance->byte_input); + // NumberInput + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewNumberInput); + number_input_free(instance->number_input); + + // DateTimeInput + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewDateTimeInput); + date_time_input_free(instance->date_time_input); + // TextBox view_dispatcher_remove_view(instance->view_dispatcher, NfcViewTextBox); text_box_free(instance->text_box); @@ -222,6 +257,9 @@ void nfc_app_free(NfcApp* instance) { furi_string_free(instance->file_path); furi_string_free(instance->file_name); + free(instance->nfc_saflok_data); + instance->nfc_saflok_data = NULL; + free(instance); } diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 4547fff2b1..1649c38716 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -15,9 +15,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include #include "views/dict_attack.h" @@ -123,6 +126,22 @@ typedef struct { size_t dict_keys_current; } NfcMfUltralightCDictContext; +typedef struct { + uint8_t card_level; + uint8_t card_type; + uint8_t card_id; + bool opening_key; + uint16_t lock_id; + uint16_t pass_number; + uint16_t sequence_and_combination; + bool deadbolt_override; + uint8_t restricted_days; + uint16_t property_id; + + DateTime creation; + DateTime expire; +} NfcSaflokData; + struct NfcApp { DialogsApp* dialogs; Storage* storage; @@ -144,9 +163,12 @@ struct NfcApp { Submenu* submenu; DialogEx* dialog_ex; Popup* popup; + VariableItemList* variable_item_list; Loading* loading; TextInput* text_input; ByteInput* byte_input; + NumberInput* number_input; + DateTimeInput* date_time_input; TextBox* text_box; Widget* widget; DetectReader* detect_reader; @@ -171,6 +193,7 @@ struct NfcApp { NfcDevice* nfc_device; Iso14443_3aData* iso14443_3a_edit_data; + NfcSaflokData* nfc_saflok_data; FuriString* file_path; FuriString* file_name; FuriTimer* timer; @@ -182,9 +205,12 @@ typedef enum { NfcViewMenu, NfcViewDialogEx, NfcViewPopup, + NfcViewVariableItemList, NfcViewLoading, NfcViewTextInput, NfcViewByteInput, + NfcViewNumberInput, + NfcViewDateTimeInput, NfcViewTextBox, NfcViewWidget, NfcViewDictAttack, @@ -196,6 +222,17 @@ typedef enum { NfcSceneSaveConfirmStateCrackNonces, } NfcSceneSaveConfirmState; +typedef enum { + // NfcSceneSaflokStateNewCard = 0 << 0, + NfcSceneSaflokStateEditCard = 1 << 0, + + // NfcSceneSaflokStateInMainView = 0 << 1, + NfcSceneSaflokStateInSubView = 1 << 1, + + // NfcSceneSaflokStateEditingDateCreation = 0 << 2, + NfcSceneSaflokStateEditingDateExpire = 1 << 2, +} NfcSceneSaflokState; + #ifdef __cplusplus extern "C" { #endif diff --git a/applications/main/nfc/plugins/supported_cards/saflok.c b/applications/main/nfc/plugins/supported_cards/saflok.c index 9cab61ee5d..4589076a50 100644 --- a/applications/main/nfc/plugins/supported_cards/saflok.c +++ b/applications/main/nfc/plugins/supported_cards/saflok.c @@ -1,5 +1,3 @@ -// KDF from: https://gitee.com/jadenwu/Saflok_KDF/blob/master/saflok.c -// KDF published and reverse engineered by Jaden Wu // FZ plugin by @noproto // Decryption and parsing from: https://gitee.com/wangshuoyue/unsaflok @@ -11,16 +9,33 @@ #include #include +#include + #include #include #include #include #include +#include "../../api/saflok/saflok_util.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" #define TAG "Saflok" -#define MAGIC_TABLE_SIZE 192 +#define SL_PROTO_INVALID (-1) +#define SL_PROTO_MFC (0) +#define SL_PROTO_UL (1) +#define SL_PROTO_TOTAL (2) + +#ifndef SL_PROTO +#error Must specify what protocol to use with SL_PROTO define! +#endif +#if SL_PROTO <= SL_PROTO_INVALID || SL_PROTO >= SL_PROTO_TOTAL +#error Invalid SL_PROTO specified! +#endif + #define KEY_LENGTH 6 #define UID_LENGTH 4 #define CHECK_SECTOR 1 @@ -78,99 +93,6 @@ static MfClassicKeyPair saflok_1k_keys[] = { {.a = 0x000000000000, .b = 0xffffffffffff}, // 015 }; -void generate_saflok_key(const uint8_t* uid, uint8_t* key) { - static const uint8_t magic_table[MAGIC_TABLE_SIZE] = { - 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xF0, 0x57, 0xB3, 0x9E, 0xE3, 0xD8, 0x00, 0x00, 0xAA, - 0x00, 0x00, 0x00, 0x96, 0x9D, 0x95, 0x4A, 0xC1, 0x57, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, - 0x8F, 0x43, 0x58, 0x0D, 0x2C, 0x9D, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xFF, 0xCC, 0xE0, - 0x05, 0x0C, 0x43, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x34, 0x1B, 0x15, 0xA6, 0x90, 0xCC, - 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x89, 0x58, 0x56, 0x12, 0xE7, 0x1B, 0x00, 0x00, 0xAA, - 0x00, 0x00, 0x00, 0xBB, 0x74, 0xB0, 0x95, 0x36, 0x58, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, - 0xFB, 0x97, 0xF8, 0x4B, 0x5B, 0x74, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xC9, 0xD1, 0x88, - 0x35, 0x9F, 0x92, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x8F, 0x92, 0xE9, 0x7F, 0x58, 0x97, - 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x16, 0x6C, 0xA2, 0xB0, 0x9F, 0xD1, 0x00, 0x00, 0xAA, - 0x00, 0x00, 0x00, 0x27, 0xDD, 0x93, 0x10, 0x1C, 0x6C, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, - 0xDA, 0x3E, 0x3F, 0xD6, 0x49, 0xDD, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x58, 0xDD, 0xED, - 0x07, 0x8E, 0x3E, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x5C, 0xD0, 0x05, 0xCF, 0xD9, 0x07, - 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x11, 0x8D, 0xD0, 0x01, 0x87, 0xD0}; - - uint8_t magic_byte = (uid[3] >> 4) + (uid[2] >> 4) + (uid[0] & 0x0F); - uint8_t magickal_index = (magic_byte & 0x0F) * 12 + 11; - - uint8_t temp_key[KEY_LENGTH] = {magic_byte, uid[0], uid[1], uid[2], uid[3], magic_byte}; - uint8_t carry_sum = 0; - - for(int i = KEY_LENGTH - 1; i >= 0; i--, magickal_index--) { - uint16_t keysum = temp_key[i] + magic_table[magickal_index] + carry_sum; - temp_key[i] = (keysum & 0xFF); - carry_sum = keysum >> 8; - } - - memcpy(key, temp_key, KEY_LENGTH); -} - -// Lookup table -static const uint8_t c_aDecode[256] = { - 0xEA, 0x0D, 0xD9, 0x74, 0x4E, 0x28, 0xFD, 0xBA, 0x7B, 0x98, 0x87, 0x78, 0xDD, 0x8D, 0xB5, - 0x1A, 0x0E, 0x30, 0xF3, 0x2F, 0x6A, 0x3B, 0xAC, 0x09, 0xB9, 0x20, 0x6E, 0x5B, 0x2B, 0xB6, - 0x21, 0xAA, 0x17, 0x44, 0x5A, 0x54, 0x57, 0xBE, 0x0A, 0x52, 0x67, 0xC9, 0x50, 0x35, 0xF5, - 0x41, 0xA0, 0x94, 0x60, 0xFE, 0x24, 0xA2, 0x36, 0xEF, 0x1E, 0x6B, 0xF7, 0x9C, 0x69, 0xDA, - 0x9B, 0x6F, 0xAD, 0xD8, 0xFB, 0x97, 0x62, 0x5F, 0x1F, 0x38, 0xC2, 0xD7, 0x71, 0x31, 0xF0, - 0x13, 0xEE, 0x0F, 0xA3, 0xA7, 0x1C, 0xD5, 0x11, 0x4C, 0x45, 0x2C, 0x04, 0xDB, 0xA6, 0x2E, - 0xF8, 0x64, 0x9A, 0xB8, 0x53, 0x66, 0xDC, 0x7A, 0x5D, 0x03, 0x07, 0x80, 0x37, 0xFF, 0xFC, - 0x06, 0xBC, 0x26, 0xC0, 0x95, 0x4A, 0xF1, 0x51, 0x2D, 0x22, 0x18, 0x01, 0x79, 0x5E, 0x76, - 0x1D, 0x7F, 0x14, 0xE3, 0x9E, 0x8A, 0xBB, 0x34, 0xBF, 0xF4, 0xAB, 0x48, 0x63, 0x55, 0x3E, - 0x56, 0x8C, 0xD1, 0x12, 0xED, 0xC3, 0x49, 0x8E, 0x92, 0x9D, 0xCA, 0xB1, 0xE5, 0xCE, 0x4D, - 0x3F, 0xFA, 0x73, 0x05, 0xE0, 0x4B, 0x93, 0xB2, 0xCB, 0x08, 0xE1, 0x96, 0x19, 0x3D, 0x83, - 0x39, 0x75, 0xEC, 0xD6, 0x3C, 0xD0, 0x70, 0x81, 0x16, 0x29, 0x15, 0x6C, 0xC7, 0xE7, 0xE2, - 0xF6, 0xB7, 0xE8, 0x25, 0x6D, 0x3A, 0xE6, 0xC8, 0x99, 0x46, 0xB0, 0x85, 0x02, 0x61, 0x1B, - 0x8B, 0xB3, 0x9F, 0x0B, 0x2A, 0xA8, 0x77, 0x10, 0xC1, 0x88, 0xCC, 0xA4, 0xDE, 0x43, 0x58, - 0x23, 0xB4, 0xA1, 0xA5, 0x5C, 0xAE, 0xA9, 0x7E, 0x42, 0x40, 0x90, 0xD2, 0xE9, 0x84, 0xCF, - 0xE4, 0xEB, 0x47, 0x4F, 0x82, 0xD4, 0xC5, 0x8F, 0xCD, 0xD3, 0x86, 0x00, 0x59, 0xDF, 0xF2, - 0x0C, 0x7C, 0xC6, 0xBD, 0xF9, 0x7D, 0xC4, 0x91, 0x27, 0x89, 0x32, 0x72, 0x33, 0x65, 0x68, - 0xAF}; - -void DecryptCard( - uint8_t strCard[BASIC_ACCESS_BYTE_NUM], - int length, - uint8_t decryptedCard[BASIC_ACCESS_BYTE_NUM]) { - int i, num, num2, num3, num4, b = 0, b2 = 0; - for(i = 0; i < length; i++) { - num = c_aDecode[strCard[i]] - (i + 1); - if(num < 0) num += 256; - decryptedCard[i] = num; - } - - if(length == 17) { - b = decryptedCard[10]; - b2 = b & 1; - } - - for(num2 = length; num2 > 0; num2--) { - b = decryptedCard[num2 - 1]; - for(num3 = 8; num3 > 0; num3--) { - num4 = num2 + num3; - if(num4 > length) num4 -= length; - int b3 = decryptedCard[num4 - 1]; - int b4 = (b3 & 0x80) >> 7; - b3 = ((b3 << 1) & 0xFF) | b2; - b2 = (b & 0x80) >> 7; - b = ((b << 1) & 0xFF) | b4; - decryptedCard[num4 - 1] = b3; - } - decryptedCard[num2 - 1] = b; - } -} - -uint8_t CalculateCheckSum(uint8_t data[BASIC_ACCESS_BYTE_NUM]) { - int sum = 0; - for(int i = 0; i < BASIC_ACCESS_BYTE_NUM - 1; i++) { - sum += data[i]; - } - sum = 255 - (sum & 0xFF); - return sum & 0xFF; -} - static bool saflok_verify(Nfc* nfc) { bool verified = false; @@ -219,7 +141,7 @@ static bool saflok_read(Nfc* nfc, NfcDevice* device) { if(uid_len != UID_LENGTH) break; uint8_t key[KEY_LENGTH]; - generate_saflok_key(uid, key); + saflok_generate_key(uid, key); uint64_t num_key = bit_lib_bytes_to_num_be(key, KEY_LENGTH); FURI_LOG_D(TAG, "Saflok: Key generated for UID: %012llX", num_key); @@ -255,27 +177,42 @@ static bool saflok_read(Nfc* nfc, NfcDevice* device) { bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); +#if SL_PROTO == SL_PROTO_MFC const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); +#elif SL_PROTO == SL_PROTO_UL + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); +#endif bool parsed = false; do { +#if SL_PROTO == SL_PROTO_MFC // Check card type if(data->type != MfClassicType1k) break; - // Verify key const MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, CHECK_SECTOR); - const uint64_t key_a = bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key_a != saflok_1k_keys[CHECK_SECTOR].a) break; - - // Decrypt basic access + // Init basic access uint8_t basicAccess[BASIC_ACCESS_BYTE_NUM]; memcpy(&basicAccess, &data->block[1].data, 16); memcpy(&basicAccess[16], &data->block[2].data[0], 1); +#elif SL_PROTO == SL_PROTO_UL + // Check card type + if(data->type != MfUltralightTypeMfulC) break; + // Init basic access + uint8_t basicAccess[BASIC_ACCESS_BYTE_NUM]; + memcpy(&basicAccess[0 * 4], &data->page[34].data, 4); + memcpy(&basicAccess[1 * 4], &data->page[35].data, 4); + memcpy(&basicAccess[2 * 4], &data->page[36].data, 4); + memcpy(&basicAccess[3 * 4], &data->page[37].data, 4); + memcpy(&basicAccess[4 * 4], &data->page[38].data[0], 1); +#endif + + // Decrypt basic access uint8_t decodedBA[BASIC_ACCESS_BYTE_NUM]; - DecryptCard(basicAccess, BASIC_ACCESS_BYTE_NUM, decodedBA); + saflok_decrypt_card(basicAccess, BASIC_ACCESS_BYTE_NUM, decodedBA); // Byte 0: Key level, LED warning bit, and subgroup functions uint8_t key_level = (decodedBA[0] & 0xF0) >> 4; @@ -290,8 +227,7 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) { uint16_t key_record = (key_record_high << 8) | decodedBA[3]; // Byte 4 & 5: Pass level in reversed binary - // This part is commented because the relevance of this info is still unknown - // uint16_t pass_level = ((decodedBA[4] & 0xFF) << 8) | decodedBA[5]; + uint16_t pass_level = ((decodedBA[4] & 0xFF) << 8) | decodedBA[5]; // uint8_t pass_levels[12]; // int pass_levels_count = 0; @@ -388,28 +324,33 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) { // Byte 16: Checksum uint8_t checksum = decodedBA[16]; - uint8_t checksum_calculated = CalculateCheckSum(decodedBA); + uint8_t checksum_calculated = saflok_calculate_checksum(decodedBA); bool checksum_valid = (checksum_calculated == checksum); - for(int i = 0; i < 17; i++) { + for(int i = 0; i < BASIC_ACCESS_BYTE_NUM; i++) { FURI_LOG_D(TAG, "%02X", decodedBA[i]); } FURI_LOG_D(TAG, "CS decrypted: %02X", checksum); FURI_LOG_D(TAG, "CS calculated: %02X", checksum_calculated); - - furi_string_cat_printf(parsed_data, "\e#Saflok Card\n"); +#if SL_PROTO == SL_PROTO_MFC + furi_string_cat_printf(parsed_data, "\e#Saflok MFC 1K Card\n"); +#elif SL_PROTO == SL_PROTO_UL + furi_string_cat_printf(parsed_data, "\e#Saflok UL-C Card\n"); +#endif + furi_string_cat_printf(parsed_data, "Property Number: %u\n", property_id); furi_string_cat_printf( parsed_data, "Key Level: %u, %s\n", key_levels[key_level].level_num, key_levels[key_level].level_name); - furi_string_cat_printf(parsed_data, "LED Exp. Warning: %s\n", led_warning ? "Yes" : "No"); furi_string_cat_printf(parsed_data, "Key ID: %02X\n", key_id); furi_string_cat_printf(parsed_data, "Key Record: %04X\n", key_record); - furi_string_cat_printf(parsed_data, "Opening key: %s\n", opening_key ? "Yes" : "No"); furi_string_cat_printf( parsed_data, "Seq. & Combination: %04X\n", sequence_combination_number); + furi_string_cat_printf(parsed_data, "Pass Level: %04X\n", pass_level); + furi_string_cat_printf(parsed_data, "Opening Key: %s\n", opening_key ? "Yes" : "No"); furi_string_cat_printf( parsed_data, "Override Deadbolt: %s\n", override_deadbolt ? "Yes" : "No"); + furi_string_cat_printf(parsed_data, "LED Exp. Warning: %s\n", led_warning ? "Yes" : "No"); furi_string_cat_printf( parsed_data, "Restricted Weekday: %s\n", @@ -430,19 +371,33 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) { expire_day, expire_hour, expire_minute); - furi_string_cat_printf(parsed_data, "Property Number: %u\n", property_id); furi_string_cat_printf(parsed_data, "Checksum Valid: %s", checksum_valid ? "Yes" : "No"); +#if SL_PROTO == SL_PROTO_MFC + // MFC returns parsed = true since we have proper verify and read functions parsed = true; +#elif SL_PROTO == SL_PROTO_UL + // UL returns parsed = checksum_valid since we don't have proper verify and read functions + // TODO: change to true after verify and read are implemented + parsed = checksum_valid; +#endif + } while(false); return parsed; } /* Actual implementation of app<>plugin interface */ static const NfcSupportedCardsPlugin saflok_plugin = { +#if SL_PROTO == SL_PROTO_MFC .protocol = NfcProtocolMfClassic, .verify = saflok_verify, .read = saflok_read, .parse = saflok_parse, +#elif SL_PROTO == SL_PROTO_UL + .protocol = NfcProtocolMfUltralight, + .verify = NULL, + .read = NULL, + .parse = saflok_parse, +#endif }; /* Plugin descriptor to comply with basic plugin specification */ @@ -456,3 +411,5 @@ static const FlipperAppPluginDescriptor saflok_plugin_descriptor = { const FlipperAppPluginDescriptor* saflok_plugin_ep(void) { return &saflok_plugin_descriptor; } + +#pragma GCC diagnostic pop diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index d498eb90c0..0825bb6c08 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -64,6 +64,8 @@ ADD_SCENE(nfc, set_sak, SetSak) ADD_SCENE(nfc, set_atqa, SetAtqa) ADD_SCENE(nfc, set_uid, SetUid) +ADD_SCENE(nfc, saflok_input, SaflokInput) + ADD_SCENE(nfc, slix_unlock_menu, SlixUnlockMenu) ADD_SCENE(nfc, slix_key_input, SlixKeyInput) ADD_SCENE(nfc, slix_unlock, SlixUnlock) diff --git a/applications/main/nfc/scenes/nfc_scene_saflok_input.c b/applications/main/nfc/scenes/nfc_scene_saflok_input.c new file mode 100644 index 0000000000..1cd2937eab --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_saflok_input.c @@ -0,0 +1,700 @@ +#include "../nfc_app_i.h" +#include "../helpers/saflok.h" + +#include + +typedef struct { + char* short_level_name; + char* level_name; +} SaflokCardLevel; + +static SaflokCardLevel card_levels[] = { + {"Guest", "Guest Key"}, + {"Cnectors", "Connectors"}, + {"Suite", "Suite"}, + {"LmtdUse", "Limited Use"}, + {"Failsafe", "Failsafe"}, + {"Inhibit", "Inhibit"}, + {"MtgMstr", "Pool/Meeting Master"}, + {"Hsekpng", "Housekeeping"}, + {"FloorKey", "Floor Key"}, + {"SctnKey", "Section Key"}, + {"RmsMstr", "Rooms Master"}, + {"GrndMstr", "Grand Master"}, + {"Emrgncy", "Emergency"}, + {"Lockout", "Electronic Lockout"}, + {"SecProg", "Secondary Programming Key"}, + {"PriProg", "Primary Programming Key"}, +}; + +typedef struct { + char* short_type_name; + char* type_name; + char* ppk_short_type_name; + char* ppk_type_name; +} SaflokCardType; + +static SaflokCardType card_types[] = { + {"Stndard", "Make Standard Key", "Stndard", "Make Standard Key"}, + {"Reseque", "Make Resequencing Key", "LED Diag", "LED Diagnostic"}, + {"Block", "Make Block Key", "E2 Chng", "Dis/Enable E2 Changes"}, + {"Unblock", "Make Unblock Key", "Erase E2", "Erase Lock (E2) Memory"}, + {"ChgDate", "Change Checkout Date", "Batt Disc", "Battery Disconect"}, + {"Chkout", "Check Out a Key", "Display", "Make Display Key"}, + {"CnclPrer", "Cancel Prereg Key", "", ""}, + {"ChknPrer", "Check In Prereg Key", "Intero", "Make Interrogation Key"}, + {"Cancel ID", "Make Cancel-A-Key-ID", "", ""}, + {"Chng Rm", "Make Change Room Key", "", ""}, +}; + +static bool available_card_types[][COUNT_OF(card_types)] = { + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // Guest Key + {1, 1, 1, 1, 1, 1, 0, 0, 1, 1}, // Connectors + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // Suite + {1, 1, 1, 1, 0, 0, 0, 0, 1, 0}, // Limited Use + {1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, // Failsafe + {1, 1, 1, 1, 0, 0, 0, 0, 1, 0}, // Inhibit + {1, 1, 1, 1, 0, 0, 0, 0, 1, 0}, // Pool/Meeting Master + {1, 1, 1, 1, 0, 0, 0, 0, 1, 0}, // Housekeeping + {1, 1, 1, 1, 0, 0, 0, 0, 1, 0}, // Floor Key + {1, 1, 1, 1, 0, 0, 0, 0, 1, 0}, // Section Key + {1, 1, 1, 1, 0, 0, 0, 0, 1, 0}, // Rooms Master + {1, 1, 1, 1, 0, 0, 0, 0, 1, 0}, // Grand Master + {1, 1, 1, 1, 0, 0, 0, 0, 1, 0}, // Emergency + {1, 1, 1, 1, 0, 0, 0, 0, 1, 0}, // Electronic Lockout + {1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Secondary Programming Key + {1, 1, 1, 1, 1, 1, 0, 1, 0, 0}, // Primary Programming Key +}; + +typedef struct { + char* short_key_name; + char* key_name; +} SaflokDisplayKey; + +static SaflokDisplayKey display_key_types[] = { + {"EPR Ver", "EPROM Version"}, + {"Clk Time", "Clock Time"}, + {"Clk Date", "Clock Date"}, + {"AutoLtc", "AutoLatch Status"}, + {"LstRcrds", "Last 2 LPI Records"}, + {"KnbSwt", "Knob Switch Status"}, + {"DBltSwt", "Dead Bolt Switch Status"}, + {"MtrSwt", "Motor Switch + Latch State"}, + {"LowBtry", "Low Battery Status"}, + {"Clck Run", "Clock Run Test"}, + {"LEDTest", "LED Lights Test"}, +}; + +static const char* options[] = { + "Card Level", + "Card Type", + "Card ID", + "Opening Key", + "Key/Lock ID", + "Pass #/Areas", + "Seq & Comb", + "Deadbolt Overide", + "Restricted Days", + "Property ID", + "Creation", + "Expiration", + "Done", +}; + +static const char* days_of_the_week[] = { + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +}; + +void set_state_flag(NfcApp* app, NfcSceneSaflokState flag, bool new_state); +void number_input_callback(void* context, int32_t number); +void submenu_item_callback(void* context, uint32_t index); +void variable_item_list_update_value(NfcApp* app, VariableItem* item, uint32_t value, bool apply); +void variable_item_list_enter_callback(void* context, uint32_t index); +void variable_item_list_change_callback(VariableItem* item); + +void date_time_done_callback(void* context) { + NfcApp* app = context; + + set_state_flag(app, NfcSceneSaflokStateInSubView, false); + view_dispatcher_switch_to_view(app->view_dispatcher, NfcViewVariableItemList); +} + +void date_time_input_callback(void* context) { + NfcApp* app = context; + + // Expire date cannot be before creation date because + // it's stored, unsigned, relative to creation + uint32_t expire = datetime_datetime_to_timestamp(&app->nfc_saflok_data->expire); + uint32_t creation = datetime_datetime_to_timestamp(&app->nfc_saflok_data->creation); + expire = MAX(expire, creation); + datetime_timestamp_to_datetime(expire, &app->nfc_saflok_data->expire); + + // Expire year cannot be more than 15 years after creation + // date because it's stored as a 4-bit unsigned int + app->nfc_saflok_data->expire.year = + MIN(app->nfc_saflok_data->expire.year, app->nfc_saflok_data->creation.year + 15); + + // Trigger callback for both dates to update their labels + for(uint8_t i = 10; i < 12; i++) { + VariableItem* item = variable_item_list_get(app->variable_item_list, i); + variable_item_list_update_value(app, item, 0, false); + } +} + +void set_state_flag(NfcApp* app, NfcSceneSaflokState flag, bool new_state) { + NfcSceneSaflokState state = + scene_manager_get_scene_state(app->scene_manager, NfcSceneSaflokInput); + if(new_state) + state |= flag; + else + state &= ~flag; + scene_manager_set_scene_state(app->scene_manager, NfcSceneSaflokInput, state); +} + +void number_input_callback(void* context, int32_t number) { + NfcApp* app = context; + + // Find the selected item and trigger the callback to update its label + uint8_t item_index = variable_item_list_get_selected_item_index(app->variable_item_list); + VariableItem* item = variable_item_list_get(app->variable_item_list, item_index); + variable_item_list_update_value(app, item, number, true); + + set_state_flag(app, NfcSceneSaflokStateInSubView, false); + view_dispatcher_switch_to_view(app->view_dispatcher, NfcViewVariableItemList); +} + +void submenu_item_callback(void* context, uint32_t index) { + NfcApp* app = context; + + // Find the selected item + uint8_t item_index = variable_item_list_get_selected_item_index(app->variable_item_list); + VariableItem* item = variable_item_list_get(app->variable_item_list, item_index); + + switch(item_index) { + // Pass #/Areas + case 5: + if(index == 12) { + // Done button + set_state_flag(app, NfcSceneSaflokStateInSubView, false); + view_dispatcher_switch_to_view(app->view_dispatcher, NfcViewVariableItemList); + } else { + // Toggle the area's bit and update the label + char state = (app->nfc_saflok_data->pass_number ^= 1 << index); + state = state ? 'X' : ' '; + + FuriString* label = furi_string_alloc(); + furi_string_printf(label, "[%c] Area %ld", state, index + 1); + submenu_change_item_label(app->submenu, index, furi_string_get_cstr(label)); + furi_string_free(label); + } + break; + // Restricted Days + case 8: + if(index == COUNT_OF(days_of_the_week)) { + // Done button + set_state_flag(app, NfcSceneSaflokStateInSubView, false); + view_dispatcher_switch_to_view(app->view_dispatcher, NfcViewVariableItemList); + } else { + // Toggle the day's bit and update the label + char state = (app->nfc_saflok_data->restricted_days ^= 1 << index); + state = state ? 'X' : ' '; + + FuriString* label = furi_string_alloc(); + furi_string_printf(label, "[%c] %s", state, days_of_the_week[index]); + submenu_change_item_label(app->submenu, index, furi_string_get_cstr(label)); + furi_string_free(label); + } + break; + // All other options just select a single item from the list + default: + variable_item_set_current_value_index(item, index); + set_state_flag(app, NfcSceneSaflokStateInSubView, false); + view_dispatcher_switch_to_view(app->view_dispatcher, NfcViewVariableItemList); + } + + // Always update the Variable Item List label + variable_item_list_update_value(app, item, index, true); +} + +void variable_item_list_update_value(NfcApp* app, VariableItem* item, uint32_t value, bool apply) { + uint8_t key = 255; + for(uint8_t i = 0; i < COUNT_OF(options); i++) { + if(variable_item_list_get(app->variable_item_list, i) == item) { + key = i; + break; + } + } + if(key >= COUNT_OF(options)) return; + + NfcSceneSaflokState state = + scene_manager_get_scene_state(app->scene_manager, NfcSceneSaflokInput); + + FuriString* value_text = furi_string_alloc(); + switch(key) { + case 0: // Card Level + if(apply) + app->nfc_saflok_data->card_level = value; + else + value = app->nfc_saflok_data->card_level; + variable_item_set_current_value_index(item, value); + variable_item_set_values_count(item, COUNT_OF(card_levels)); + furi_string_printf(value_text, "%s", card_levels[value].short_level_name); + + // Update available card types + VariableItem* _card_type_item = variable_item_list_get(app->variable_item_list, 1); + if(_card_type_item) + variable_item_list_update_value( + app, _card_type_item, app->nfc_saflok_data->card_type, true); + break; + + case 1: // Card Type + // Enforce available_card_types list by skipping over unavailable types + uint32_t last_value = app->nfc_saflok_data->card_type; + bool direction = value > last_value; + while(!available_card_types[app->nfc_saflok_data->card_level][value]) { + // Prevent overflow/underflow + if(value >= COUNT_OF(card_types)) direction = 0; + if(value == 0) direction = 1; + + // Skip type + if(direction) + value++; + else + value--; + } + + // Find # of highest available card type + uint8_t max_value = 0; + for(uint8_t i = 0; i < COUNT_OF(card_types); i++) + if(available_card_types[app->nfc_saflok_data->card_level][i]) max_value = i; + + if(apply) + app->nfc_saflok_data->card_type = value; + else + value = app->nfc_saflok_data->card_type; + variable_item_set_current_value_index(item, value); + variable_item_set_values_count(item, max_value + 1); + + if(app->nfc_saflok_data->card_level != 15) { + furi_string_printf(value_text, "%s", card_types[value].short_type_name); + } else { + furi_string_printf(value_text, "%s", card_types[value].ppk_short_type_name); + + // Reset card ID to ID=0 if Display Key was just selected + if(last_value != 5 && value == 5) app->nfc_saflok_data->card_id = 0; + } + + // Update available card IDs + VariableItem* _card_id_item = variable_item_list_get(app->variable_item_list, 2); + if(_card_id_item) + variable_item_list_update_value( + app, _card_id_item, app->nfc_saflok_data->card_id, true); + + break; + case 2: // Card ID + if(apply) + app->nfc_saflok_data->card_id = value; + else + value = app->nfc_saflok_data->card_id; + if(app->nfc_saflok_data->card_level == 15 && app->nfc_saflok_data->card_type == 5) { + // Is a display key, use display key list for title + variable_item_set_current_value_index(item, value); + variable_item_set_values_count(item, COUNT_OF(display_key_types)); + furi_string_printf(value_text, "%s", display_key_types[value].short_key_name); + } else { + variable_item_set_values_count(item, 0); + furi_string_printf(value_text, "%ld", value); + } + break; + case 3: // Opening Key + if(apply) + app->nfc_saflok_data->opening_key = value; + else + value = app->nfc_saflok_data->opening_key; + variable_item_set_current_value_index(item, value); + variable_item_set_values_count(item, 2); + + if(value) { + furi_string_printf(value_text, "On"); + } else { + furi_string_printf(value_text, "Off"); + } + break; + case 4: // Key/Lock ID + if(apply) + app->nfc_saflok_data->lock_id = value; + else + value = app->nfc_saflok_data->lock_id; + furi_string_printf(value_text, "%ld", value); + break; + case 5: // Pass #/Areas + uint8_t num_selected = 0; + for(uint8_t i = 0; i < 12; i++) { + if(app->nfc_saflok_data->pass_number & (1 << i)) num_selected++; + } + + // Make a right arrow always show on the list item + variable_item_set_current_value_index(item, 0); + variable_item_set_values_count(item, 2); + + // If we're on the main view and tried to change to value=1, + // the user pressed the right arrow, so enter the subview + if(!(state & NfcSceneSaflokStateInSubView) && apply && value == 1) + variable_item_list_enter_callback(app, 5); + + furi_string_printf(value_text, "%d/12", num_selected); + break; + case 6: // Seq & Comb + if(apply) + app->nfc_saflok_data->sequence_and_combination = value; + else + value = app->nfc_saflok_data->sequence_and_combination; + furi_string_printf(value_text, "%ld", value); + break; + + case 7: // Deadbolt Overide + if(apply) + app->nfc_saflok_data->deadbolt_override = value; + else + value = app->nfc_saflok_data->deadbolt_override; + + variable_item_set_current_value_index(item, value); + variable_item_set_values_count(item, 2); + + if(value) { + furi_string_printf(value_text, "On"); + } else { + furi_string_printf(value_text, "Off"); + } + break; + + case 8: // Restricted Days + uint8_t num_restricted = 0; + for(uint8_t i = 0; i < 7; i++) { + if(app->nfc_saflok_data->restricted_days & (1 << i)) num_restricted++; + } + + // Make a right arrow always show on the list item + variable_item_set_current_value_index(item, 0); + variable_item_set_values_count(item, 2); + + // If we're on the main view and tried to change to value=1, + // the user pressed the right arrow, so enter the subview + if(!(state & NfcSceneSaflokStateInSubView) && apply && value == 1) + variable_item_list_enter_callback(app, 8); + + furi_string_printf(value_text, "%d/7", num_restricted); + break; + + case 9: // Property ID + if(apply) + app->nfc_saflok_data->property_id = value; + else + value = app->nfc_saflok_data->property_id; + + furi_string_printf(value_text, "%ld", value); + break; + + case 10: // Creation + furi_string_printf( + value_text, + "%04d-%02d-%02d %02d:%02d", + app->nfc_saflok_data->creation.year, + app->nfc_saflok_data->creation.month, + app->nfc_saflok_data->creation.day, + app->nfc_saflok_data->creation.hour, + app->nfc_saflok_data->creation.minute); + break; + case 11: // Expiration + furi_string_printf( + value_text, + "%04d-%02d-%02d %02d:%02d", + app->nfc_saflok_data->expire.year, + app->nfc_saflok_data->expire.month, + app->nfc_saflok_data->expire.day, + app->nfc_saflok_data->expire.hour, + app->nfc_saflok_data->expire.minute); + break; + + case 12: // Done + break; + } + variable_item_set_current_value_text(item, furi_string_get_cstr(value_text)); + furi_string_free(value_text); +} + +void variable_item_list_enter_callback(void* context, uint32_t index) { + NfcApp* app = context; + + // Reset submenu + submenu_reset(app->submenu); + + // Some options use a Submenu, others use a NumberInput + bool use_number_input = false; + bool use_date_time_input = false; + int32_t number_input_max = 0; + int32_t number_input_current = 0; + uint8_t submenu_selected_item = 0; + FuriString* label = furi_string_alloc(); + + switch(index) { + case 0: // Card Level + for(uint8_t i = 0; i < COUNT_OF(card_levels); i++) { + submenu_add_item( + app->submenu, card_levels[i].level_name, i, submenu_item_callback, context); + } + submenu_selected_item = app->nfc_saflok_data->card_level; + break; + case 1: // Card Type + bool is_ppk = app->nfc_saflok_data->card_level == 15; + for(uint8_t i = 0; i < COUNT_OF(card_types); i++) { + if(available_card_types[app->nfc_saflok_data->card_level][i]) + submenu_add_item( + app->submenu, + is_ppk ? card_types[i].ppk_type_name : card_types[i].type_name, + i, + submenu_item_callback, + context); + } + submenu_selected_item = app->nfc_saflok_data->card_type; + break; + + case 2: // Card ID + if(app->nfc_saflok_data->card_level == 15 && app->nfc_saflok_data->card_type == 5) { + // Is display key, show submenu instead + for(uint8_t i = 0; i < COUNT_OF(display_key_types); i++) { + submenu_add_item( + app->submenu, display_key_types[i].key_name, i, submenu_item_callback, context); + } + submenu_selected_item = app->nfc_saflok_data->card_id; + } else { + use_number_input = true; + number_input_current = app->nfc_saflok_data->card_id; + number_input_max = 255; + } + break; + case 3: // Opening Key + // This option can't be clicked (it's just a boolean, there's no need) + return; + + case 4: // Key/Lock ID + use_number_input = true; + number_input_current = app->nfc_saflok_data->lock_id; + number_input_max = 16383; + break; + case 5: // Pass #/Areas + for(uint8_t i = 0; i < 12; i++) { + char state = app->nfc_saflok_data->pass_number & (1 << i); + state = state ? 'X' : ' '; + + furi_string_reset(label); + furi_string_printf(label, "[%c] Area %d", state, i + 1); + submenu_add_item( + app->submenu, furi_string_get_cstr(label), i, submenu_item_callback, context); + } + + submenu_add_item(app->submenu, "Done", 12, submenu_item_callback, context); + break; + case 6: // Seq & Comb + use_number_input = true; + number_input_current = app->nfc_saflok_data->sequence_and_combination; + number_input_max = 4095; + break; + + case 7: // Deadbolt Override + // This option can't be clicked (it's just a boolean, there's no need) + return; + + case 8: // Restricted Days + for(uint8_t i = 0; i < COUNT_OF(days_of_the_week); i++) { + char state = app->nfc_saflok_data->restricted_days & (1 << i); + state = state ? 'X' : ' '; + + furi_string_reset(label); + furi_string_printf(label, "[%c] %s", state, days_of_the_week[i]); + submenu_add_item( + app->submenu, furi_string_get_cstr(label), i, submenu_item_callback, context); + } + + submenu_add_item( + app->submenu, "Done", COUNT_OF(days_of_the_week), submenu_item_callback, context); + break; + + case 9: // Property ID + use_number_input = true; + number_input_current = app->nfc_saflok_data->property_id; + number_input_max = 4095; + break; + + case 10: // Creation + date_time_input_set_result_callback( + app->date_time_input, + date_time_input_callback, + date_time_done_callback, + context, + &app->nfc_saflok_data->creation); + + use_date_time_input = true; + break; + case 11: // Expire + date_time_input_set_result_callback( + app->date_time_input, + date_time_input_callback, + date_time_done_callback, + context, + &app->nfc_saflok_data->expire); + + use_date_time_input = true; + break; + case 12: // Done + view_dispatcher_send_custom_event(app->view_dispatcher, SceneManagerEventTypeCustom); + break; + } + furi_string_free(label); + + // Switch to the appropriate view + set_state_flag(app, NfcSceneSaflokStateInSubView, true); + if(use_number_input) { + number_input_set_header_text(app->number_input, options[index]); + number_input_set_result_callback( + app->number_input, + number_input_callback, + context, + number_input_current, + 0, + number_input_max); + view_dispatcher_switch_to_view(app->view_dispatcher, NfcViewNumberInput); + } else if(use_date_time_input) { + date_time_input_set_editable_fields( + app->date_time_input, + true, + true, + true, + true, + true, + // Prevent editing seconds to make it clear they aren't used + false); + view_dispatcher_switch_to_view(app->view_dispatcher, NfcViewDateTimeInput); + } else { + submenu_set_header(app->submenu, options[index]); + submenu_set_selected_item(app->submenu, submenu_selected_item); + view_dispatcher_switch_to_view(app->view_dispatcher, NfcViewMenu); + } +} + +void variable_item_list_change_callback(VariableItem* item) { + NfcApp* app = variable_item_get_context(item); + uint8_t value = variable_item_get_current_value_index(item); + + uint8_t key = 255; + for(uint8_t i = 0; i < COUNT_OF(options); i++) { + if(variable_item_list_get(app->variable_item_list, i) == item) { + key = i; + break; + } + } + if(key >= COUNT_OF(options)) return; + + variable_item_list_update_value(app, item, value, true); +} + +void nfc_scene_saflok_input_on_enter(void* context) { + NfcApp* app = context; + VariableItem* item; + + NfcSceneSaflokState state = + scene_manager_get_scene_state(app->scene_manager, NfcSceneSaflokInput); + // Check if we're making a new card + if((state & NfcSceneSaflokStateEditCard) == 0) { + // Reset all fields to 0 + app->nfc_saflok_data->card_level = 0; + app->nfc_saflok_data->card_type = 0; + app->nfc_saflok_data->card_id = 0; + app->nfc_saflok_data->opening_key = 0; + app->nfc_saflok_data->lock_id = 0; + app->nfc_saflok_data->pass_number = 0; + app->nfc_saflok_data->sequence_and_combination = 0; + app->nfc_saflok_data->deadbolt_override = 0; + app->nfc_saflok_data->restricted_days = 0; + app->nfc_saflok_data->property_id = 0; + + // Set creation date/time to now + DateTime* datetime = malloc(sizeof(DateTime)); + furi_hal_rtc_get_datetime(datetime); + app->nfc_saflok_data->creation.year = datetime->year; + app->nfc_saflok_data->creation.month = datetime->month; + app->nfc_saflok_data->creation.day = datetime->day; + app->nfc_saflok_data->creation.hour = datetime->hour; + app->nfc_saflok_data->creation.minute = datetime->minute; + + // Set expiration date/time to a week from now + // Convert to timestamp and back to let datetime.c + // handle days per month and leap years and such + uint32_t timestamp = datetime_datetime_to_timestamp(datetime); + timestamp += 60 /*secs*/ * 60 /*mins*/ * 24 /*hours*/ * 7 /*days*/; + datetime_timestamp_to_datetime(timestamp, datetime); + + app->nfc_saflok_data->expire.year = datetime->year; + app->nfc_saflok_data->expire.month = datetime->month; + app->nfc_saflok_data->expire.day = datetime->day; + app->nfc_saflok_data->expire.hour = datetime->hour; + app->nfc_saflok_data->expire.minute = datetime->minute; + + // Switch scene state to edit so we don't erase everything if we return + state |= NfcSceneSaflokStateEditCard; + scene_manager_set_scene_state(app->scene_manager, NfcSceneSaflokInput, state); + + // Select the first item + variable_item_list_set_selected_item(app->variable_item_list, 0); + } + + for(uint8_t i = 0; i < COUNT_OF(options); i++) { + item = variable_item_list_add( + app->variable_item_list, options[i], 0, variable_item_list_change_callback, context); + variable_item_set_current_value_index(item, 0); + variable_item_list_update_value(app, item, 0, false); + } + + variable_item_list_set_enter_callback( + app->variable_item_list, variable_item_list_enter_callback, context); + + set_state_flag(app, NfcSceneSaflokStateInSubView, false); + view_dispatcher_switch_to_view(app->view_dispatcher, NfcViewVariableItemList); +} + +bool nfc_scene_saflok_input_on_event(void* context, SceneManagerEvent event) { + NfcApp* app = context; + bool consumed = false; + + NfcSceneSaflokState state = + scene_manager_get_scene_state(app->scene_manager, NfcSceneSaflokInput); + + if(event.type == SceneManagerEventTypeBack) { + if(state & NfcSceneSaflokStateInSubView) { + set_state_flag(app, NfcSceneSaflokStateInSubView, false); + view_dispatcher_switch_to_view(app->view_dispatcher, NfcViewVariableItemList); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeCustom) { + // Generate the actual data from the input values + saflok_generate_mf_classic(app->nfc_device, app->nfc_saflok_data); + set_state_flag(app, NfcSceneSaflokStateInSubView, false); + scene_manager_next_scene(app->scene_manager, NfcSceneReadMenu); + consumed = true; + } + return consumed; +} + +void nfc_scene_saflok_input_on_exit(void* context) { + NfcApp* app = context; + + // Clear view + variable_item_list_reset(app->variable_item_list); + submenu_reset(app->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index 6997775886..7a923d5da0 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -6,6 +6,8 @@ enum SubmenuIndex { SubmenuIndexGeneratorsStart, SubmenuIndexNFCA4 = NfcDataGeneratorTypeNum, SubmenuIndexNFCA7, + + SubmenuIndexSaflokMfClassic, }; static void nfc_scene_set_type_init_edit_data(Iso14443_3aData* data, size_t uid_len) { @@ -32,6 +34,13 @@ void nfc_scene_set_type_on_enter(void* context) { nfc_protocol_support_common_submenu_callback, instance); + submenu_add_item( + submenu, + "Saflok on Mifare Classic", + SubmenuIndexSaflokMfClassic, + nfc_protocol_support_common_submenu_callback, + instance); + for(size_t i = 0; i < NfcDataGeneratorTypeNum; i++) { const char* name = nfc_data_generator_get_name(i); submenu_add_item(submenu, name, i, nfc_protocol_support_common_submenu_callback, instance); @@ -53,6 +62,11 @@ bool nfc_scene_set_type_on_event(void* context, SceneManagerEvent event) { nfc_scene_set_type_init_edit_data(instance->iso14443_3a_edit_data, 4); scene_manager_next_scene(instance->scene_manager, NfcSceneSetSak); consumed = true; + } else if(event.event == SubmenuIndexSaflokMfClassic) { + nfc_scene_set_type_init_edit_data(instance->iso14443_3a_edit_data, 4); + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSaflokInput, 0); + scene_manager_next_scene(instance->scene_manager, NfcSceneSaflokInput); + consumed = true; } else { nfc_data_generator_fill_data(event.event, instance->nfc_device); scene_manager_set_scene_state( diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 6ff592a851..eed6cf40b2 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -354,12 +354,7 @@ def _validate_app_imports(target, source, env): "I_Suica_", ), # nfc_app_api_table - ( - "nfc_", - "gallagher", - "social_moscow", - "troika", - ): ( + ("nfc_", "gallagher", "social_moscow", "troika", "saflok"): ( "gallagher_deobfuscate_and_parse_credential", "GALLAGHER_CARDAX_ASCII", "mosgortrans_parse_transport_block", @@ -371,6 +366,9 @@ def _validate_app_imports(target, source, env): "nfc_protocol_support_common_on_event_empty", "nfc_unlock_helper_setup_from_state", "nfc_unlock_helper_card_detected_handler", + "saflok_calculate_checksum", + "saflok_generate_key", + "saflok_decrypt_card", ), # totp app_api_table ("totp_",): (