diff --git a/client/src/cmdhf14b.c b/client/src/cmdhf14b.c index 9d22777f16..ea7df2569c 100644 --- a/client/src/cmdhf14b.c +++ b/client/src/cmdhf14b.c @@ -2631,15 +2631,15 @@ int CmdHF14BNdefRead(const char *Cmd) { } // take offset from response uint8_t offset = response[1]; - + // Parse CC data uint8_t cc_data[resplen - 2]; memcpy(cc_data, response, sizeof(cc_data)); uint8_t file_id[2] = {cc_data[9], cc_data[10]}; - + uint16_t max_rapdu_size = (cc_data[3] << 8 | cc_data[4]) - 2; - + max_rapdu_size = max_rapdu_size < sizeof(response) - 2 ? max_rapdu_size : sizeof(response) - 2; // --------------- NDEF file reading ---------------- uint8_t aSELECT_FILE_NDEF[30]; @@ -2739,7 +2739,7 @@ int CmdHF14BNdefRead(const char *Cmd) { PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf 14b ndefread -v`") " for more details"); // So far this prints absolutely nothing } - + // get total NDEF length before save. If fails, we save it all size_t n = 0; if (NDEFGetTotalLength(response + 2, resplen - 4, &n) != PM3_SUCCESS) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index c6d6c7694e..c7ffab56c9 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -5008,7 +5008,7 @@ void printKeyTableEx(size_t sectorscnt, sector_t *e_sector, uint8_t start_sector _YELLOW_("H") ":Hardnested / " _YELLOW_("C") ":statiCnested / " _YELLOW_("A") ":keyA " - " )" + " )" ); if (sectorscnt == 18) { PrintAndLogEx(INFO, "( " _MAGENTA_("*") " ) These sectors used for signature. Lays outside of user memory"); diff --git a/client/src/cmdhfsaflok.c b/client/src/cmdhfsaflok.c index 2758eea568..3ba666868a 100644 --- a/client/src/cmdhfsaflok.c +++ b/client/src/cmdhfsaflok.c @@ -19,6 +19,9 @@ #include #include #include +#include +#include +#include #include "cliparser.h" #include "cmdparser.h" #include "comms.h" @@ -30,9 +33,719 @@ #include "generator.h" #include "cmdhfmf.h" -#define KEY_LENGTH 6 +// MiFARE Classic encoded with SafeLok encoded data +// defining this structure makes it more clear what +// is passed around, and avoids the need to pass lengths +// to many of the functions. +static const uint8_t _days_in_month_lookup[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; -static const uint8_t c_aDecode[256] = { +static inline uint8_t get_days_in_month(uint16_t year, uint8_t month) { + if (month < 1 || month > 12) { // programmer error ... this function uses 1-based indexing! + PrintAndLogEx(ERR, "Invalid month lookup: %d, expected range [1..12] ... returning 0 days!", month); + return 0; + } + + uint8_t days = _days_in_month_lookup[month]; + // Adjust for leap years + if ((month == 2u) && + (year % 4u == 0u) && + ((year % 100u != 0u) || (year % 400u == 0u)) + ) { + days = 29u; + } + return days; +} + +// TODO: consider separate structs for encrypted vs. decrypted +// for even greater type safety. +typedef struct _saflok_mfc_data_t { + uint8_t raw[17]; +} saflok_mfc_data_t; +typedef struct _saflok_mfc_uid_t { + uint8_t uid[4]; +} saflok_mfc_uid_t; +typedef struct _saflok_mfc_key_t { + uint8_t key[6]; +} saflok_mfc_key_t; +typedef struct _saflok_mfc_datetime_t { + uint16_t year; // full year, e.g., 2024 + uint8_t month; // Month in range [1..12] + uint8_t day; // day of month, max range [1..31] + uint8_t hour; // hour of day, range [0..23] + uint8_t minute; // minute of hour, range [0..59] +} saflok_mfc_datetime_t; +typedef struct _saflok_mfc_datetime_offset_t { + uint8_t years; // range [0..15] + uint8_t months; // range [0..11] + uint8_t days; // maximum range [0..31] + uint8_t hours; // range [0..23] + uint8_t minutes; // range [0..59] +} saflok_mfc_datetime_offset_t; + +#if 1 // test cases for self-test +typedef struct _saflok_test_case_t { + saflok_mfc_data_t encoded; + saflok_mfc_data_t decoded; + saflok_mfc_datetime_t card_creation_datetime; + saflok_mfc_datetime_offset_t card_interval; + saflok_mfc_datetime_t card_expiration_datetime; + uint16_t lock_id; + uint16_t pass_number; + uint16_t sequence_and_combination; + uint16_t property_id; + uint8_t card_level; + uint8_t card_type; + uint8_t card_id; + uint8_t opening_key; + uint8_t restricted_days; + bool deadbolt_override; + uint8_t checksum; +} saflok_test_case_t; + +static const saflok_test_case_t saflok_test_cases[] = { + { // 5A4A845610B6B1DD8F4753872BC0E91095 / F7B08101FFF7FF9E10500072A00023E7C7 + // Tests interval that crosses a leap year using days + .encoded = {.raw = {0x5A, 0x4A, 0x84, 0x56, 0x10, 0xB6, 0xB1, 0xDD, 0x8F, 0x47, 0x53, 0x87, 0x2B, 0xC0, 0xE9, 0x10, 0x95} }, + .decoded = {.raw = {0xF7, 0xB0, 0x81, 0x01, 0xFF, 0xF7, 0xFF, 0x9E, 0x10, 0x50, 0x00, 0x72, 0xA0, 0x00, 0x23, 0xE7, 0xC7} }, + .card_expiration_datetime = { .year = 2020, .month = 3, .day = 1, .hour = 0, .minute = 0 }, + .card_creation_datetime = { .year = 2019, .month = 2, .day = 20, .hour = 0, .minute = 0 }, + .card_interval = { .years = 1, .months = 0, .days = 10, .hours = 0, .minutes = 0 }, + .lock_id = 257, + .pass_number = 4095, + .sequence_and_combination = 2047, + .property_id = 999, + .card_level = 15, + .card_type = 7, + .card_id = 176, + .opening_key = 2, + .restricted_days = 30, // 0b0011110 + .deadbolt_override = true, + .checksum = 0xC7, + }, + { // 3FB01B9F0100B55A7D423B6166C0E980B9 / B5AD8101FF57EB9400000A82EDF723E7CC + // Tests interval overflow from minutes -> hours -> day -> leap year overflow (2/29 -> 3/1) + .encoded = {.raw = {0x3F, 0xB0, 0x1B, 0x9F, 0x01, 0x00, 0xB5, 0x5A, 0x7D, 0x42, 0x3B, 0x61, 0x66, 0xC0, 0xE9, 0x80, 0xB9} }, + .decoded = {.raw = {0xB5, 0xAD, 0x81, 0x01, 0xFF, 0x57, 0xEB, 0x94, 0x00, 0x00, 0x0A, 0x82, 0xED, 0xF7, 0x23, 0xE7, 0xCC} }, + .card_expiration_datetime = { .year = 2020, .month = 3, .day = 1, .hour = 0, .minute = 5 }, + .card_creation_datetime = { .year = 2020, .month = 2, .day = 29, .hour = 23, .minute = 55 }, + .card_interval = { .years = 0, .months = 0, .days = 0, .hours = 0, .minutes = 10 }, + .lock_id = 257, + .pass_number = 4085, + .sequence_and_combination = 2027, + .property_id = 999, + .card_level = 11, + .card_type = 5, + .card_id = 173, + .opening_key = 2, + .restricted_days = 20, // 0b0010100 + .deadbolt_override = true, + .checksum = 0xCC, + }, + { // C2FE64DFE4EBB1CC7D8F6FEBA6E59E5B4D / 338341A3C777557F00080082E000207B4E + // Tests interval overflow into a final leap-day expiration date + .encoded = {.raw = {0xC2, 0xFE, 0x64, 0xDF, 0xE4, 0xEB, 0xB1, 0xCC, 0x7D, 0x8F, 0x6F, 0xEB, 0xA6, 0xE5, 0x9E, 0x5B, 0x4D} }, + .decoded = {.raw = {0x33, 0x83, 0x41, 0xA3, 0xC7, 0x77, 0x55, 0x7F, 0x00, 0x08, 0x00, 0x82, 0xE0, 0x00, 0x20, 0x7B, 0x4E} }, + .card_expiration_datetime = { .year = 2020, .month = 2, .day = 29, .hour = 0, .minute = 0 }, + .card_creation_datetime = { .year = 2020, .month = 2, .day = 28, .hour = 0, .minute = 0 }, + .card_interval = { .years = 0, .months = 0, .days = 1, .hours = 0, .minutes = 0 }, + .lock_id = 419, + .pass_number = 3191, + .sequence_and_combination = 1877, + .property_id = 123, + .card_level = 3, + .card_type = 3, + .card_id = 131, + .opening_key = 1, + .restricted_days = 127, // 0b1111111 + .deadbolt_override = false, + .checksum = 0x4E, + }, +}; + +#endif // test cases for self-test + +#define KEY_LENGTH sizeof(saflok_mfc_key_t) + +static uint32_t get_bitfield(const saflok_mfc_data_t *data, size_t start_bit, size_t num_bits); +static bool set_bitfield(saflok_mfc_data_t *data, size_t start_bit, size_t num_bits, uint32_t value); + + + +#if 1 // getters and setters for each bitfield in saflok_mfc_data_t + + static inline uint8_t get_saflok_mfc_card_level(const saflok_mfc_data_t *data) { + return (uint8_t)get_bitfield(data, 0, 4); + } + static inline bool set_saflok_mfc_card_level(saflok_mfc_data_t *data, uint32_t card_level) { + if (card_level > 0xFu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " card_level out of range (%08x)", card_level); + return false; + } + bool success = set_bitfield(data, 0, 4, card_level); + return success; + } + static inline uint8_t get_saflok_mfc_card_type(const saflok_mfc_data_t *data) { + return (uint8_t)get_bitfield(data, 4, 4); + } + static inline bool set_saflok_mfc_card_type(saflok_mfc_data_t *data, uint32_t card_type) { + if (card_type > 0xFu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " card_type out of range (%08x)", card_type); + return false; + } + bool success = set_bitfield(data, 4, 4, card_type); + return success; + } + static inline uint8_t get_saflok_mfc_card_id(const saflok_mfc_data_t *data) { + return (uint8_t)get_bitfield(data, 8, 8); + } + static inline bool set_saflok_mfc_card_id(saflok_mfc_data_t *data, uint32_t card_id) { + if (card_id > 0xFFu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " card_id out of range (%08x)", card_id); + return false; + } + bool success = set_bitfield(data, 8, 8, card_id); + return success; + } + static inline uint8_t get_saflok_mfc_opening_key(const saflok_mfc_data_t *data) { + return (uint8_t)get_bitfield(data, 16, 2); + } + static inline bool set_saflok_mfc_opening_key(saflok_mfc_data_t *data, uint32_t opening_key) { + if (opening_key > 0x3u) { + PrintAndLogEx(ERR, _RED_("ERROR:") " opening_key out of range (%08x)", opening_key); + return false; + } + bool success = set_bitfield(data, 16, 2, opening_key); + return success; + } + static inline uint16_t get_saflok_mfc_lock_id(const saflok_mfc_data_t *data) { + return (uint16_t)get_bitfield(data, 18, 14); + } + static inline bool set_saflok_mfc_lock_id(saflok_mfc_data_t *data, uint32_t lock_id) { + if (lock_id > 0x3FFFu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " lock_id out of range (%08x)", lock_id); + return false; + } + bool success = set_bitfield(data, 18, 14, lock_id); + return success; + } + static inline uint16_t get_saflok_mfc_pass_number(const saflok_mfc_data_t *data) { + return (uint16_t)get_bitfield(data, 32, 12); + } + static inline bool set_saflok_mfc_pass_number(saflok_mfc_data_t *data, uint32_t pass_number) { + if (pass_number > 0xFFFu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " pass_number out of range (%08x)", pass_number); + return false; + } + bool success = set_bitfield(data, 32, 12, pass_number); + return success; + } + static inline uint16_t get_saflok_mfc_sequence_and_combination(const saflok_mfc_data_t *data) { + return (uint16_t)get_bitfield(data, 44, 12); + } + static inline bool set_saflok_mfc_sequence_and_combination(saflok_mfc_data_t *data, uint32_t sequence_and_combination) { + if (sequence_and_combination > 0xFFFu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " sequence_and_combination out of range (%08x)", sequence_and_combination); + return false; + } + bool success = set_bitfield(data, 44, 12, sequence_and_combination); + return success; + } + static inline bool get_saflok_mfc_deadbolt_override(const saflok_mfc_data_t *data) { + uint32_t result = get_bitfield(data, 56, 1); + return (result != 0); + } + static inline bool set_saflok_mfc_deadbolt_override(saflok_mfc_data_t *data, uint32_t deadbolt_override) { + bool success = set_bitfield(data, 56, 1, deadbolt_override ? 0x1 : 0x0); + return success; + } + static inline uint8_t get_saflok_mfc_restricted_days(const saflok_mfc_data_t *data) { + return (uint8_t)get_bitfield(data, 57, 7); + } + static inline bool set_saflok_mfc_restricted_days(saflok_mfc_data_t *data, uint32_t restricted_days) { + if (restricted_days > 0x7Fu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " restricted_days out of range (%08x)", restricted_days); + return false; + } + bool success = set_bitfield(data, 57, 7, restricted_days); + return success; + } + static inline uint32_t get_saflok_mfc_raw_interval(const saflok_mfc_data_t *data) { + return get_bitfield(data, 64, 24); + } + static inline bool set_saflok_mfc_raw_interval(saflok_mfc_data_t *data, uint32_t raw_interval) { + if (raw_interval > 0xFFFFFFu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " raw_interval out of range (%08x)", raw_interval); + return false; + } + bool success = set_bitfield(data, 64, 24, raw_interval); + return success; + } + static inline uint32_t get_saflok_mfc_raw_card_creation_date(const saflok_mfc_data_t *data) { + return get_bitfield(data, 88, 28); + } + static inline bool set_saflok_mfc_raw_card_creation_date(saflok_mfc_data_t *data, uint32_t raw_card_creation_date) { + if (raw_card_creation_date > 0x0FFFFFFFu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " raw_card_creation_date out of range (%08x)", raw_card_creation_date); + return false; + } + bool success = set_bitfield(data, 88, 28, raw_card_creation_date); + return success; + } + static inline uint16_t get_saflok_mfc_property_id(const saflok_mfc_data_t *data) { + return get_bitfield(data, 116, 12); + } + static inline bool set_saflok_mfc_property_id(saflok_mfc_data_t *data, uint32_t property_id) { + if (property_id > 0xFFFu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " property_id out of range (%08x)", property_id); + return false; + } + bool success = set_bitfield(data, 116, 12, property_id); + return success; + } + static inline uint8_t get_saflok_mfc_checksum(const saflok_mfc_data_t *data) { + return data->raw[16]; + } + static inline bool set_saflok_mfc_checksum(saflok_mfc_data_t *data, uint8_t checksum) { + data->raw[16] = checksum; + return true; + } +#endif // getters and setters for each bitfield in saflok_mfc_data_t + +#if 1 // helpers for get/set_saflok_mfc_card_creation_date() ... do not call these directly as does not validate... + static inline uint16_t _get_saflok_mfc_card_creation_year_impl(const saflok_mfc_data_t *data) { + uint8_t creation_year_bits_high = (data->raw[14] & 0x70u); + uint8_t creation_year_bits_low = (data->raw[11] & 0xF0u) >> 4; + uint8_t creation_year_bits = creation_year_bits_high | creation_year_bits_low; + uint16_t creation_year = 1980u + creation_year_bits; // automatically extends to uint16_t + return creation_year; + } + static inline bool _set_saflok_mfc_card_creation_year_impl(saflok_mfc_data_t *data, uint16_t year) { + if (year < 1980u || year > (1980u+0x7Fu)) { + PrintAndLogEx(ERR, _RED_("ERROR:") " year out of range (%u)", year); + return false; + } + uint16_t year_bits = year - 1980u; + uint8_t creation_year_bits_high = year_bits & 0x70u; + uint8_t creation_year_bits_low = (year_bits & 0x0Fu) << 4; + data->raw[14] = (data->raw[14] & 0x8Fu) | creation_year_bits_high; + data->raw[11] = (data->raw[11] & 0x0Fu) | creation_year_bits_low; + return true; + } + static inline uint8_t _get_saflok_mfc_card_creation_month_impl(const saflok_mfc_data_t *data) { + // should be in range [1..12] ... value of zero is invalid + return (data->raw[11] & 0x0Fu); + } + static inline bool _set_saflok_mfc_card_creation_month_impl(saflok_mfc_data_t *data, uint8_t month) { + if (month < 1 || month > 12) { + PrintAndLogEx(ERR, _RED_("ERROR:") " month out of range (%u)", month); + return false; + } + data->raw[11] = (data->raw[11] & 0xF0u) | (month & 0x0Fu); + return true; + } + static inline uint8_t _get_saflok_mfc_card_creation_day_impl(const saflok_mfc_data_t *data) { + return ((data->raw[12] >> 3) & 0x1Fu); + } + static inline bool _set_saflok_mfc_card_creation_day_impl(saflok_mfc_data_t *data, uint8_t day) { + data->raw[12] = (data->raw[12] & 0x07u) | ((day & 0x1Fu) << 3); + return true; + } + static inline uint8_t _get_saflok_mfc_card_creation_hour_impl(const saflok_mfc_data_t *data) { + // low three bits from raw[12] + // high two bits from raw[13] + return (((data->raw[12] & 0x07u) << 2) | (data->raw[13] >> 6)); + } + static inline bool _set_saflok_mfc_card_creation_hour_impl(saflok_mfc_data_t *data, uint8_t hour) { + data->raw[12] = (data->raw[12] & 0xF8u) | ((hour >> 2) & 0x07u); + data->raw[13] = (data->raw[13] & 0x3Fu) | ((hour & 0x03u) << 6); + return true; + } + static inline uint8_t _get_saflok_mfc_card_creation_minute_impl(const saflok_mfc_data_t *data) { + return (data->raw[13] & 0x3Fu); + } + static inline bool _set_saflok_mfc_card_creation_minute_impl(saflok_mfc_data_t *data, uint8_t minute) { + data->raw[13] = (data->raw[13] & 0xC0u) | (minute & 0x3Fu); + return true; + } +#endif // // helpers for get/set_saflok_mfc_card_creation_date() ... do not call these directly as does not validate... +#if 1 // getters and setters for creation date + + static inline bool is_saflok_mfc_datetime_valid(const saflok_mfc_datetime_t *date) { + bool result = true; + if ((date->year < 1980) || (date->year > (1980u+0xFFu)) ) { + PrintAndLogEx(WARNING, "year out of range (%d)", date->year); + result = false; + } + if ((date->month < 1) || (date->month > 12)) { + PrintAndLogEx(WARNING, "month out of range (%d)", date->month); + result = false; + } + uint8_t max_days = get_days_in_month(date->year, date->month); + if ((date->day < 1u) || (date->day > max_days)) { + PrintAndLogEx(WARNING, "day out of range (%d) for month %d year %d", date->day, date->month, date->year); + result = false; + } + if (date->hour > 23) { + PrintAndLogEx(WARNING, "hour out of range (%d)", date->hour); + result = false; + } + if (date->minute > 59) { + PrintAndLogEx(WARNING, "minute out of range (%d)", date->minute); + result = false; + } + return result; + } + static inline saflok_mfc_datetime_t get_saflok_mfc_card_creation_datetime( + const saflok_mfc_data_t *data + ) { + saflok_mfc_datetime_t date = { + .year = _get_saflok_mfc_card_creation_year_impl(data), + .month = _get_saflok_mfc_card_creation_month_impl(data), + .day = _get_saflok_mfc_card_creation_day_impl(data), + .hour = _get_saflok_mfc_card_creation_hour_impl(data), + .minute = _get_saflok_mfc_card_creation_minute_impl(data) + }; + if (!is_saflok_mfc_datetime_valid(&date)) { + PrintAndLogEx(WARNING, "Warning: creation date appears invalid: %04d-%02d-%02dT%02d:%02d", + date.year, date.month, date.day, date.hour, date.minute + ); + } + return date; + } + static inline bool set_saflok_mfc_card_creation_datetime(saflok_mfc_data_t * data, const saflok_mfc_datetime_t * date) { + if (!is_saflok_mfc_datetime_valid(date)) { + PrintAndLogEx(ERR, _RED_("ERROR:") " creation date is invalid"); + return false; + } + bool result = + _set_saflok_mfc_card_creation_year_impl(data, date->year) && + _set_saflok_mfc_card_creation_month_impl(data, date->month) && + _set_saflok_mfc_card_creation_day_impl(data, date->day) && + _set_saflok_mfc_card_creation_hour_impl(data, date->hour) && + _set_saflok_mfc_card_creation_minute_impl(data, date->minute); + if (!result) { + PrintAndLogEx(ERR, _RED_("ERROR:") " failed to set creation date of %04d-%02d-%02dT%02d:%02d", + date->year, date->month, date->day, date->hour, date->minute + ); + } + return result; + } +#endif // getters and setters for creation date components +#if 1 // helpers for get/set_saflok_mfc_expiration_date() + static inline uint8_t _get_saflok_mfc_interval_years_impl(const saflok_mfc_data_t *data) { + // supports up to 15 year intervals(!) + return (data->raw[8] >> 4); + } + static inline bool _set_saflok_mfc_interval_years_impl(saflok_mfc_data_t *data, uint32_t years) { + if (years > 0xFu) { + PrintAndLogEx(ERR, _RED_("ERROR:") " interval years out of range (%u)", years); + return false; + } + data->raw[8] = (data->raw[8] & 0x0Fu) | ((years & 0xFFu) << 4); + return true; + } + static inline uint8_t _get_saflok_mfc_interval_months_impl(const saflok_mfc_data_t *data) { + uint8_t months = data->raw[8] & 0x0Fu; + if (months >= 12u) { + PrintAndLogEx(WARNING, "Warning: interval months appears invalid (%u)", months); + } + return months; + } + static inline bool _set_saflok_mfc_interval_months_impl(saflok_mfc_data_t *data, uint8_t months) { + if (months >= 12u) { + PrintAndLogEx(ERR, _RED_("ERROR:") " interval months out of range (%u)", months); + return false; + } + data->raw[8] = (data->raw[8] & 0xF0u) | (months & 0x0Fu); + return true; + } + static inline uint8_t _get_saflok_mfc_interval_days_impl(const saflok_mfc_data_t *data) { + uint8_t days = (data->raw[9] >> 3) & 0x1Fu; + if (days > 31u) { + PrintAndLogEx(WARNING, "Warning: interval days appears invalid (%u)", days); + } + return days; + } + static inline bool _set_saflok_mfc_interval_days_impl(saflok_mfc_data_t *data, uint8_t days) { + if (days > 31u) { + PrintAndLogEx(ERR, _RED_("ERROR:") " interval days out of range (%u)", days); + return false; + } + data->raw[9] = (data->raw[9] & 0x07u) | ((days & 0x1Fu) << 3); + return true; + } + static inline uint8_t _get_saflok_mfc_interval_hours_impl(const saflok_mfc_data_t *data) { + // low three bits from raw[9] + // top two bits from raw[10] + uint8_t hours = ((data->raw[9] & 0x07u) << 2) | (data->raw[10] >> 6); + if (hours > 23u) { + PrintAndLogEx(WARNING, "Warning: interval hours appears invalid (%u)", hours); + } + return hours; + } + static inline bool _set_saflok_mfc_interval_hours_impl(saflok_mfc_data_t *data, uint8_t hours) { + if (hours > 23u) { + PrintAndLogEx(ERR, _RED_("ERROR:") " interval hours out of range (%u)", hours); + return false; + } + // five bit value split across two bytes: + // three MSB go into the low three bits from raw[9] + // two LSB go into the top two bits from raw[10] + data->raw[9] = (data->raw[9] & 0xF8u) | ((hours >> 2) & 0x07u); + data->raw[10] = (data->raw[10] & 0x3Fu) | ((hours & 0x03u) << 6); + return true; + } + static inline uint8_t _get_saflok_mfc_interval_minutes_impl(const saflok_mfc_data_t *data) { + uint8_t minutes = (data->raw[10] & 0x3Fu); + if (minutes > 59u) { + PrintAndLogEx(WARNING, "Warning: interval minutes appears invalid (%u)", minutes); + } + return minutes; + } + static inline bool _set_saflok_mfc_interval_minutes_impl(saflok_mfc_data_t *data, uint8_t minutes) { + if (minutes > 59u) { + PrintAndLogEx(ERR, _RED_("ERROR:") " interval minutes out of range (%u)", minutes); + return false; + } + data->raw[10] = (data->raw[10] & 0xC0u) | (minutes & 0x3Fu); + return true; + } +#endif // getters for interval (expiration) components +#if 1 // Setting the interval date is based on the creation date. + + static saflok_mfc_datetime_offset_t get_saflok_mfc_interval( + const saflok_mfc_data_t *data + ) { + saflok_mfc_datetime_offset_t offset = { + .years = _get_saflok_mfc_interval_years_impl(data), + .months = _get_saflok_mfc_interval_months_impl(data), + .days = _get_saflok_mfc_interval_days_impl(data), + .hours = _get_saflok_mfc_interval_hours_impl(data), + .minutes = _get_saflok_mfc_interval_minutes_impl(data) + }; + return offset; + } + static bool set_saflok_mfc_interval( + saflok_mfc_data_t *data, + saflok_mfc_datetime_offset_t offset + ) { + bool result = + _set_saflok_mfc_interval_years_impl(data, offset.years) && + _set_saflok_mfc_interval_months_impl(data, offset.months) && + _set_saflok_mfc_interval_days_impl(data, offset.days) && + _set_saflok_mfc_interval_hours_impl(data, offset.hours) && + _set_saflok_mfc_interval_minutes_impl(data, offset.minutes); + return result; + } + static saflok_mfc_datetime_t add_offset( + const saflok_mfc_datetime_t *base, + const saflok_mfc_datetime_offset_t offset + ) { + + static const int MY_DEBUG_LEVEL = DEBUG; + // adding dates is non-trivial. + // luckily, there's already a standard for this... + struct tm tm = { + .tm_year = base->year - 1900, // years since 1900 + .tm_mon = base->month - 1, // range: [0..11] + .tm_mday = base->day, // range: [1..31] + .tm_hour = base->hour, // range: [0..23] + .tm_min = base->minute, // range: [0..59] + .tm_sec = 30, // selecting midpoint to avoid leapseconds changing minute + .tm_isdst = 0, // UTC has no DST + }; + + PrintAndLogEx(MY_DEBUG_LEVEL, "Base date: %04u-%02u-%02uT%02u:%02u", + base->year, base->month, base->day, + base->hour, base->minute + ); + PrintAndLogEx(MY_DEBUG_LEVEL, "As struct tm: %04d-%02d-%02dT%02d:%02d", + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min + ); + PrintAndLogEx(MY_DEBUG_LEVEL, "Offset to add: %u years, %u months, %u days, %u hours, %u minutes", + offset.years, offset.months, offset.days, + offset.hours, offset.minutes + ); + + // add the offsets ... and it's OK for them to be out of range + tm.tm_year += offset.years; + tm.tm_mon += offset.months; + tm.tm_mday += offset.days; + tm.tm_hour += offset.hours; + tm.tm_min += offset.minutes; + + PrintAndLogEx(MY_DEBUG_LEVEL, "With Offset (pre-normalization): %04d-%02d-%02dT%02d:%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min + ); + + if (tm.tm_min >= 60) { + tm.tm_hour += tm.tm_min / 60; + tm.tm_min = tm.tm_min % 60; + PrintAndLogEx(MY_DEBUG_LEVEL, "Normalized minutes: %04d-%02d-%02dT%02d:%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min + ); + + } + if (tm.tm_hour >= 24) { + tm.tm_mday += tm.tm_hour / 24; + tm.tm_hour = tm.tm_hour % 24; + PrintAndLogEx(MY_DEBUG_LEVEL, "Normalized hours: %04d-%02d-%02dT%02d:%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min + ); + } + // variable days per month ... loop is simplest option + for (uint8_t days_in_month = get_days_in_month(tm.tm_year + 1900, tm.tm_mon + 1); + tm.tm_mday > days_in_month; + days_in_month = get_days_in_month(tm.tm_year + 1900, tm.tm_mon + 1) + ) { + tm.tm_mon ++; + tm.tm_mday -= days_in_month; + if (tm.tm_mon >= 12) { + tm.tm_year ++; + tm.tm_mon -= 12; + } + PrintAndLogEx(MY_DEBUG_LEVEL, "Normalized days: %04d-%02d-%02dT%02d:%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min + ); + } + if (tm.tm_mon >= 12) { + tm.tm_year += tm.tm_mon / 12; + tm.tm_mon = tm.tm_mon % 12; + PrintAndLogEx(MY_DEBUG_LEVEL, "Normalized months: %04d-%02d-%02dT%02d:%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min + ); + } + PrintAndLogEx(MY_DEBUG_LEVEL, "Final normalized date: %04d-%02d-%02dT%02d:%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min + ); + + // and finally convert back into saflok_mfc_datetime_t + saflok_mfc_datetime_t result = { + .year = tm.tm_year + 1900, + .month = tm.tm_mon + 1, + .day = tm.tm_mday, + .hour = tm.tm_hour, + .minute = tm.tm_min + }; + PrintAndLogEx(MY_DEBUG_LEVEL, "Converted back to saflok_mfc_datetime_t: %04d-%02d-%02dT%02d:%02d", + result.year, result.month, result.day, + result.hour, result.minute + ); + return result; + } + static saflok_mfc_datetime_offset_t get_datetime_offset( + const saflok_mfc_datetime_t *start, + const saflok_mfc_datetime_t *end + ) { + + enum logLevel MYDBG = DEBUG; + saflok_mfc_datetime_offset_t result = {0}; + + if (!is_saflok_mfc_datetime_valid(start)) { + PrintAndLogEx(ERR, _RED_("ERROR:") " start date is invalid"); + return result; + } + if (!is_saflok_mfc_datetime_valid(end)) { + PrintAndLogEx(ERR, _RED_("ERROR:") " end date is invalid"); + return result; + } + + PrintAndLogEx(MYDBG, "Calculating offset from %04u-%02u-%02uT%02u:%02u to %04u-%02u-%02uT%02u:%02u", + start->year, start->month, start->day, start->hour, start->minute, + end->year, end->month, end->day, end->hour, end->minute + ); + // use `struct tm`, which uses signed integers for each part + // and thus allows simpler checks for borrowing + struct tm diff = { + .tm_year = end->year - start->year, + .tm_mon = end->month - start->month, + .tm_mday = end->day - start->day, + .tm_hour = end->hour - start->hour, + .tm_min = end->minute - start->minute, + }; + PrintAndLogEx(MYDBG, "Initial diff: %d years, %d months, %d days, %d hours, %d minutes", + diff.tm_year, diff.tm_mon, diff.tm_mday, diff.tm_hour, diff.tm_min + ); + if (diff.tm_min < 0) { + diff.tm_min += 60; + diff.tm_hour -= 1; + } + if (diff.tm_hour < 0) { + diff.tm_hour += 24; + diff.tm_mday -= 1; + } + if (diff.tm_mday < 0) { + // determine days in previous month + uint16_t tgt_year = end->year - (end->month == 1 ? 1 : 0); + uint8_t tgt_month = (end->month == 1 ? 12 : (end->month - 1)); + uint8_t days_in_prev_month = get_days_in_month(tgt_year, tgt_month); + PrintAndLogEx(MYDBG, "Borrowing days from month %02u of year %04u which has %02u days", + tgt_month, tgt_year, days_in_prev_month + ); + + // borrow from previous month + diff.tm_mon -= 1; + diff.tm_mday += days_in_prev_month; + } + if (diff.tm_mon < 0) { + diff.tm_mon += 12; + diff.tm_year -= 1; + } + + result.years = diff.tm_year; + result.months = diff.tm_mon; + result.days = diff.tm_mday; + result.hours = diff.tm_hour; + result.minutes = diff.tm_min; + PrintAndLogEx(MYDBG, "Final offset: %d years, %d months, %d days, %d hours, %d minutes", + result.years, result.months, result.days, result.hours, result.minutes + ); + return result; + } + static saflok_mfc_datetime_t get_saflok_mfc_card_expiration_datetime( + const saflok_mfc_data_t *data + ) { + static const int MY_DEBUG_LEVEL = DEBUG; + saflok_mfc_datetime_t creation_date = get_saflok_mfc_card_creation_datetime(data); + saflok_mfc_datetime_offset_t offset = get_saflok_mfc_interval(data); + PrintAndLogEx(MY_DEBUG_LEVEL, "Card Creation Date: %04u-%02u-%02uT%02u:%02u", + creation_date.year, creation_date.month, creation_date.day, + creation_date.hour, creation_date.minute + ); + PrintAndLogEx(MY_DEBUG_LEVEL, "Card Interval Offset: %u years, %u months, %u days, %u hours, %u minutes", + offset.years, offset.months, offset.days, + offset.hours, offset.minutes + ); + saflok_mfc_datetime_t expiration = add_offset(&creation_date, offset); + PrintAndLogEx(MY_DEBUG_LEVEL, "Calculated Expiration Date: %04u-%02u-%02uT%02u:%02u", + expiration.year, expiration.month, expiration.day, + expiration.hour, expiration.minute + ); + return expiration; + } + static bool set_saflok_mfc_card_expiration_datetime( + saflok_mfc_data_t *data, + const saflok_mfc_datetime_t * expiration + ) { + if (!is_saflok_mfc_datetime_valid(expiration)) { + PrintAndLogEx(ERR, _RED_("ERROR:") " expiration date is invalid"); + return false; + } + saflok_mfc_datetime_t creation_datetime = get_saflok_mfc_card_creation_datetime(data); + saflok_mfc_datetime_offset_t offset = get_datetime_offset(&creation_datetime, expiration); + bool result = set_saflok_mfc_interval(data, offset); + return result; + } +#endif // Setting the interval date is based on the creation date. + +static const uint8_t c_aDecode[] = { 234, 13, 217, 116, 78, 40, 253, 186, 123, 152, 135, 120, 221, 141, 181, 26, 14, 48, 243, 47, 106, 59, 172, 9, 185, 32, 110, 91, 43, 182, @@ -60,8 +773,9 @@ static const uint8_t c_aDecode[256] = { 12, 124, 198, 189, 249, 125, 196, 145, 39, 137, 50, 114, 51, 101, 104, 175 }; +_Static_assert(ARRAYLEN(c_aDecode) == 256, "c_aDecode must have 256 elements"); -static const uint8_t c_aEncode[256] = { +static const uint8_t c_aEncode[] = { 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, @@ -89,8 +803,9 @@ static const uint8_t c_aEncode[256] = { 74, 111, 239, 18, 129, 44, 180, 56, 90, 244, 151, 64, 104, 6, 49, 103 }; +_Static_assert(ARRAYLEN(c_aEncode) == 256, "c_aEncode must have 256 elements"); -uint8_t magic_table[192] = { +uint8_t magic_table[] = { 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, @@ -106,115 +821,156 @@ uint8_t magic_table[192] = { 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 + 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x11, 0x8D, 0xD0, 0x01, 0x87, 0xD0, }; +_Static_assert(ARRAYLEN(magic_table) == 192, "magic_table must have 192 elements"); static const char *level_names[] = { - "Guest Key", // Index 0 - "Connectors", // Index 1 - "Suite", // Index 2 - "Limited Use", // Index 3 - "Failsafe", // Index 4 - "Inhibit", // Index 5 - "Pool/Meeting Master", // Index 6 - "Housekeeping", // Index 7 - "Floor Key", // Index 8 - "Section Key", // Index 9 - "Rooms Master", // Index 10 - "Grand Master", // Index 11 - "Emergency", // Index 12 - "Electronic Lockout", // Index 13 + "Guest Key", // Index 0 + "Connectors", // Index 1 + "Suite", // Index 2 + "Limited Use", // Index 3 + "Failsafe", // Index 4 + "Inhibit", // Index 5 + "Pool/Meeting Master", // Index 6 + "Housekeeping", // Index 7 + "Floor Key", // Index 8 + "Section Key", // Index 9 + "Rooms Master", // Index 10 + "Grand Master", // Index 11 + "Emergency", // Index 12 + "Electronic Lockout", // Index 13 "Secondary Programming Key (SPK)", // Index 14 - "Primary Programming Key (PPK)", // Index 15 + "Primary Programming Key (PPK)", // Index 15 }; +_Static_assert(ARRAYLEN(level_names) == 16, "level_names must have 16 elements"); static int CmdHelp(const char *Cmd); -static void saflok_decrypt(uint8_t *strCard, int length, uint8_t *decryptedCard) { +static void saflok_decrypt(const saflok_mfc_data_t *encryptedCard, saflok_mfc_data_t *decryptedCard) { + static const size_t length = ARRAYLEN(encryptedCard->raw); for (int i = 0; i < length; i++) { - int num = c_aDecode[strCard[i]] - (i + 1); + int num = c_aDecode[encryptedCard->raw[i]] - (i + 1); if (num < 0) { num += 256; } - decryptedCard[i] = num; + decryptedCard->raw[i] = num; } int b = 0; int b2 = 0; - if (length == 17) { - b = decryptedCard[10]; + if (length == 17) { // True for saflok_mfc_data_t + b = decryptedCard->raw[10]; b2 = b & 1; } for (int num2 = length; num2 > 0; num2--) { - b = decryptedCard[num2 - 1]; + b = decryptedCard->raw[num2 - 1]; for (int num3 = 8; num3 > 0; num3--) { int num4 = num2 + num3; if (num4 > length) { num4 -= length; } - int b3 = decryptedCard[num4 - 1]; + int b3 = decryptedCard->raw[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->raw[num4 - 1] = b3; } - decryptedCard[num2 - 1] = b; + decryptedCard->raw[num2 - 1] = b; } } - - -static void saflok_encrypt(uint8_t *keyCard, int length, uint8_t *encryptedCard) { +static void saflok_encrypt(const saflok_mfc_data_t *plaintext, saflok_mfc_data_t *encryptedCard) { + static const size_t length = ARRAYLEN(plaintext->raw); int b = 0; - memcpy(encryptedCard, keyCard, length); + memcpy(encryptedCard->raw, plaintext->raw, length); for (int i = 0; i < length; i++) { - int b2 = encryptedCard[i]; + int b2 = encryptedCard->raw[i]; int num2 = i; for (int j = 0; j < 8; j++) { num2 += 1; if (num2 >= length) { num2 -= length; } - int b3 = encryptedCard[num2]; + int b3 = encryptedCard->raw[num2]; int b4 = b2 & 1; b2 = (b2 >> 1) | (b << 7); b = b3 & 1; b3 = (b3 >> 1) | (b4 << 7); - encryptedCard[num2] = b3; + encryptedCard->raw[num2] = b3; } - encryptedCard[i] = b2; + encryptedCard->raw[i] = b2; } if (length == 17) { - int b2 = encryptedCard[10]; + int b2 = encryptedCard->raw[10]; b2 |= b; - encryptedCard[10] = b2; + encryptedCard->raw[10] = b2; } for (int i = 0; i < length; i++) { - int j = encryptedCard[i] + (i + 1); + int j = encryptedCard->raw[i] + (i + 1); if (j > 255) { j -= 256; } - encryptedCard[i] = c_aEncode[j]; + encryptedCard->raw[i] = c_aEncode[j]; } } +static uint32_t get_bitfield(const saflok_mfc_data_t *data, size_t start_bit, size_t num_bits) { + static const size_t total_available_bits = ARRAYLEN(data->raw) * 8; + if (start_bit >= total_available_bits) { + // Out of bounds access + PrintAndLogEx(ERR, "get_bitfield: out of bounds access (start_bit=%zu, total_available_bits=%zu)", start_bit, total_available_bits); + return 0; + } + if (num_bits > 32) { + // Exceeds maximum supported bit extraction + PrintAndLogEx(ERR, "get_bitfield: out of bounds access (num_bits=%zu, max=32)", num_bits); + return 0; + } + if (total_available_bits - start_bit < num_bits) { + // Out of bounds access + PrintAndLogEx(ERR, "get_bitfield: out of bounds access (start_bit=%zu + num_bits=%zu > total_available_bits=%zu)", start_bit, num_bits, total_available_bits); + return 0; + } -static uint32_t extract_bits(const uint8_t *data, size_t start_bit, size_t num_bits) { uint32_t result = 0; for (size_t i = 0; i < num_bits; i++) { size_t byte_index = (start_bit + i) / 8; size_t bit_index = (start_bit + i) % 8; - if (data[byte_index] & (1 << (7 - bit_index))) { + if (data->raw[byte_index] & (1 << (7 - bit_index))) { result |= (1ULL << (num_bits - 1 - i)); } } return result; } -static void insert_bits(uint8_t *data, size_t start_bit, size_t num_bits, uint32_t value) { +static bool set_bitfield(saflok_mfc_data_t *data, size_t start_bit, size_t num_bits, uint32_t value) { + static const size_t total_available_bits = ARRAYLEN(data->raw) * 8; + if (start_bit >= total_available_bits) { + // Out of bounds access + PrintAndLogEx(ERR, "set_bitfield: out of bounds access (start_bit=%zu, total_available_bits=%zu)", start_bit, total_available_bits); + return false; + } + if (num_bits > 32) { + // Exceeds maximum supported bit extraction + PrintAndLogEx(ERR, "set_bitfield: num_bits exceeds 32"); + return false; + } + if (total_available_bits - start_bit < num_bits) { + // Out of bounds access + PrintAndLogEx(ERR, "set_bitfield: out of bounds access (start_bit=%zu + num_bits=%zu > total_available_bits=%zu)", start_bit, num_bits, total_available_bits); + return false; + } + if ((num_bits < 32) && ((value >> num_bits) != 0)) { + // Value exceeds the size of the specified bit field + PrintAndLogEx(ERR, "set_bitfield: value exceeds bit field size (value=%u (0x%08x), num_bits=%zu)", value, value, num_bits); + return false; + } + + for (size_t i = 0; i < num_bits; i++) { size_t current_bit = start_bit + i; size_t byte_index = current_bit / 8; @@ -222,143 +978,75 @@ static void insert_bits(uint8_t *data, size_t start_bit, size_t num_bits, uint32 uint32_t bit_value = (value >> (num_bits - 1 - i)) & 1U; - data[byte_index] = (data[byte_index] & ~(1 << bit_index)) | (bit_value << bit_index); + data->raw[byte_index] = (data->raw[byte_index] & ~(1 << bit_index)) | (bit_value << bit_index); } + return true; } +// Static analysis hint: reads all bytes of `data[len]` +// * NOT THREAD SAFE ... uses a static buffer for result, +// so multiple calls will overwrite previous results. +// This style of design SPAWNS BUGS. +// NOTE: apparently accepted for this legacy codebase. static char *bytes_to_hex(const uint8_t *data, size_t len) { - static char buf[256]; + static char buf[256]; // WARNING: caller must immediately use or copy the result, and it's still not thread-safe! + static const size_t maximum_data_len = (ARRAYLEN(buf) / 2) - 1; // leave room for null terminator + + if (len > maximum_data_len) { + PrintAndLogEx(ERR, "saflok bytes_to_hex: input length %zu exceeds maximum supported %zu, results will be truncated!", len, maximum_data_len); + len = maximum_data_len; + } + memset(buf, 0, sizeof(buf)); // Clear the buffer before use for (size_t i = 0; i < len; i++) { sprintf(buf + (i * 2), "%02X", data[i]); } - buf[len * 2] = '\0'; return buf; } - -static int pack_datetime_expr(char *exp_datetime, uint8_t *data) { - int year, month, day, hour, minute; - - if (sscanf(exp_datetime, "%4d-%2d-%2dT%2d:%2d", - &year, &month, &day, &hour, &minute) != 5) { - return -1; - } - - data[8] = ((year & 0x0F) << 4) | (month & 0x0F); - data[9] = ((day & 0x1F) << 3) | ((hour & 0x1C) >> 2); - data[10] = ((hour & 0x03) << 6) | (minute & 0x3F); - - return 0; -} - -static int pack_datetime(char *datetime_str, uint8_t *data) { - int year, month, day, hour, minute; - - if (sscanf(datetime_str, "%4d-%2d-%2dT%2d:%2d", - &year, &month, &day, &hour, &minute) != 5) { - return -1; - } - - uint8_t year_offset = year - 1980; - - data[11] = ((year_offset & 0x0F) << 4) | (month & 0x0F); - data[12] = ((day & 0x1F) << 3) | ((hour & 0x1C) >> 2); - data[13] = ((hour & 0x03) << 6) | (minute & 0x3F); - data[14] = (data[14] & 0x0F) | ((year_offset & 0x70) << 0); - - return 0; -} - - -static uint8_t saflok_checksum(unsigned char *data, int length) { +static uint8_t calculated_saflok_checksum(const saflok_mfc_data_t *data) { + _Static_assert(ARRAYLEN(data->raw) == 17, "saflok_mfc_data_t raw size must be 17 bytes"); int sum = 0; - for (int i = 0; i < length; i++) { - sum += data[i]; + for (int i = 0; i < 16; i++) { + sum += data->raw[i]; } sum = 255 - (sum & 0xFF); return sum & 0xFF; } -static void saflok_kdf(const uint8_t *uid, uint8_t *key_out) { +static void saflok_kdf(const saflok_mfc_uid_t *uid, saflok_mfc_key_t *key_out) { - uint8_t magic_byte = (uid[3] >> 4) + (uid[2] >> 4) + (uid[0] & 0x0F); + uint8_t magic_byte = (uid->uid[3] >> 4) + (uid->uid[2] >> 4) + (uid->uid[0] & 0x0F); uint8_t magickal_index = (magic_byte & 0x0F) * 12 + 11; uint8_t carry_sum = 0; - uint8_t key[KEY_LENGTH] = {magic_byte, uid[0], uid[1], uid[2], uid[3], magic_byte}; + saflok_mfc_key_t key = {{magic_byte, uid->uid[0], uid->uid[1], uid->uid[2], uid->uid[3], magic_byte}}; for (int i = KEY_LENGTH - 1; i >= 0; i--, magickal_index--) { - uint16_t keysum = key[i] + magic_table[magickal_index]; - key[i] = (keysum & 0xFF) + carry_sum; + uint16_t keysum = key.key[i] + magic_table[magickal_index]; + key.key[i] = (keysum & 0xFF) + carry_sum; carry_sum = keysum >> 8; } - memcpy(key_out, key, KEY_LENGTH); + memcpy(key_out, &key, KEY_LENGTH); } -static void saflok_decode(uint8_t *data) { - - uint32_t card_level = extract_bits(data, 0, 4); - uint32_t card_type = extract_bits(data, 4, 4); - uint32_t card_id = extract_bits(data, 8, 8); - uint32_t opening_key = extract_bits(data, 16, 2); - uint32_t lock_id = extract_bits(data, 18, 14); - uint32_t pass_number = extract_bits(data, 32, 12); - uint32_t sequence_and_combination = extract_bits(data, 44, 12); - uint32_t deadbolt_override = extract_bits(data, 56, 1); - uint32_t restricted_days = extract_bits(data, 57, 7); - //uint32_t expire_date = extract_bits(data, 64, 24); - //uint32_t card_creation_date = extract_bits(data, 88, 28); - uint32_t property_id = extract_bits(data, 116, 12); - uint32_t checksum = extract_bits(data, 128, 8); - - //date parsing, stolen from flipper code - uint16_t interval_year = (data[8] >> 4); - uint8_t interval_month = data[8] & 0x0F; - uint8_t interval_day = (data[9] >> 3) & 0x1F; - uint8_t interval_hour = ((data[9] & 0x07) << 2) | (data[10] >> 6); - uint8_t interval_minute = data[10] & 0x3F; - - uint8_t creation_year_bits = (data[14] & 0xF0); - uint16_t creation_year = - (creation_year_bits | ((data[11] & 0xF0) >> 4)) + 1980; - uint8_t creation_month = data[11] & 0x0F; - uint8_t creation_day = (data[12] >> 3) & 0x1F; - uint8_t creation_hour = ((data[12] & 0x07) << 2) | (data[13] >> 6); - uint8_t creation_minute = data[13] & 0x3F; - - uint16_t expire_year = creation_year + interval_year; - uint8_t expire_month = creation_month + interval_month; - uint8_t expire_day = creation_day + interval_day; - uint8_t expire_hour = interval_hour; - uint8_t expire_minute = interval_minute; - - // Handle month rollover - while (expire_month > 12) { - expire_month -= 12; - expire_year++; - } - - // Handle day rollover - static const uint8_t days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - while (true) { - uint8_t max_days = days_in_month[expire_month - 1]; - // Adjust for leap years - if (expire_month == 2 && - (expire_year % 4 == 0 && (expire_year % 100 != 0 || expire_year % 400 == 0))) { - max_days = 29; - } - if (expire_day <= max_days) { - break; - } - expire_day -= max_days; - expire_month++; - if (expire_month > 12) { - expire_month = 1; - expire_year++; - } - } - +static void saflok_decode(const saflok_mfc_data_t *data) { + + uint32_t card_level = get_saflok_mfc_card_level(data); + uint32_t card_type = get_saflok_mfc_card_type(data); + uint32_t card_id = get_saflok_mfc_card_id(data); + uint32_t opening_key = get_saflok_mfc_opening_key(data); + uint32_t lock_id = get_saflok_mfc_lock_id(data); + uint32_t pass_number = get_saflok_mfc_pass_number(data); + uint32_t sequence_and_combination = get_saflok_mfc_sequence_and_combination(data); + uint32_t deadbolt_override = get_saflok_mfc_deadbolt_override(data); + uint32_t restricted_days = get_saflok_mfc_restricted_days(data); + saflok_mfc_datetime_t creation_date = get_saflok_mfc_card_creation_datetime(data); + saflok_mfc_datetime_t expiration_date = get_saflok_mfc_card_expiration_datetime(data); + uint32_t property_id = get_saflok_mfc_property_id(data); + uint32_t checksum = get_saflok_mfc_checksum(data); + + PrintAndLogEx(SUCCESS, "---- NEW routines ----" ); PrintAndLogEx(SUCCESS, "Card Level: " _GREEN_("%u (%s)"), card_level, level_names[card_level]); PrintAndLogEx(SUCCESS, "Card Type: " _GREEN_("%u"), card_type); PrintAndLogEx(SUCCESS, "Card ID: " _GREEN_("%u"), card_id); @@ -369,66 +1057,92 @@ static void saflok_decode(uint8_t *data) { PrintAndLogEx(SUCCESS, "Deadbolt Override: " _GREEN_("%u"), deadbolt_override); PrintAndLogEx(SUCCESS, "Restricted Days: " _GREEN_("%u"), restricted_days); PrintAndLogEx(SUCCESS, "Card Creation Date: " _GREEN_("%u-%02d-%02d %02d:%02d"), - creation_year, - creation_month, - creation_day, - creation_hour, - creation_minute); + creation_date.year, + creation_date.month, + creation_date.day, + creation_date.hour, + creation_date.minute); PrintAndLogEx(SUCCESS, "Expire Date: " _GREEN_("%u-%02d-%02d %02d:%02d"), - expire_year, - expire_month, - expire_day, - expire_hour, - expire_minute); + expiration_date.year, + expiration_date.month, + expiration_date.day, + expiration_date.hour, + expiration_date.minute); PrintAndLogEx(SUCCESS, "Property ID: " _GREEN_("%u"), property_id); - PrintAndLogEx(SUCCESS, "Checksum: " _GREEN_("0x%X") " (%s)", checksum, (checksum == saflok_checksum(data, 16)) ? _GREEN_("ok") : _RED_("bad")); + PrintAndLogEx(SUCCESS, "Checksum: " _GREEN_("0x%X") " (%s)", checksum, (checksum == calculated_saflok_checksum(data)) ? _GREEN_("ok") : _RED_("bad")); PrintAndLogEx(NORMAL, ""); } -static void saflok_encode(uint8_t *data, uint32_t card_level, uint32_t card_type, uint32_t card_id, - uint32_t opening_key, uint32_t lock_id, uint32_t pass_number, - uint32_t sequence_and_combination, uint32_t deadbolt_override, - uint32_t restricted_days, uint32_t expire_date, uint32_t card_creation_date, - uint32_t property_id, char *dt_e, char *dt) { - insert_bits(data, 0, 4, card_level); - insert_bits(data, 4, 4, card_type); - insert_bits(data, 8, 8, card_id); - insert_bits(data, 16, 2, opening_key); - insert_bits(data, 18, 14, lock_id); - insert_bits(data, 32, 12, pass_number); - insert_bits(data, 44, 12, sequence_and_combination); - insert_bits(data, 56, 1, deadbolt_override); - insert_bits(data, 57, 7, restricted_days); - insert_bits(data, 64, 24, expire_date); - insert_bits(data, 88, 28, card_creation_date); - insert_bits(data, 116, 12, property_id); - - int year, month, day, hour, minute; - - if (sscanf(dt, "%4d-%2d-%2dT%2d:%2d", - &year, &month, &day, &hour, &minute) == 5) { - pack_datetime(dt, data); - } - //else{ - //insert_bits(data, 88, 28,card_creation_date); - //PrintAndLogEx(SUCCESS, "DT BITS INSERTED"); - //} - - if (sscanf(dt_e, "%4d-%2d-%2dT%2d:%2d", - &year, &month, &day, &hour, &minute) == 5) { - pack_datetime_expr(dt_e, data); - } - //else{ - //insert_bits(data, 64, 24, expire_date); - //PrintAndLogEx(SUCCESS, "DTE BITS INSERTED"); - //} - - uint8_t checksum = saflok_checksum(data, 16); - insert_bits(data, 128, 8, checksum); +static void saflok_encode( + saflok_mfc_data_t *data, + uint32_t card_level, + uint32_t card_type, + uint32_t card_id, + uint32_t opening_key, + uint32_t lock_id, + uint32_t pass_number, + uint32_t sequence_and_combination, + uint32_t deadbolt_override, + uint32_t restricted_days, + uint32_t raw_interval, + uint32_t raw_card_creation_date, + uint32_t property_id, + char *dt_expiration, + char *dt + ) { + set_saflok_mfc_card_level(data, card_level); + set_saflok_mfc_card_type(data, card_type); + set_saflok_mfc_card_id(data, card_id); + set_saflok_mfc_opening_key(data, opening_key); + set_saflok_mfc_lock_id(data, lock_id); + set_saflok_mfc_pass_number(data, pass_number); + set_saflok_mfc_sequence_and_combination(data, sequence_and_combination); + set_saflok_mfc_deadbolt_override(data, deadbolt_override); + set_saflok_mfc_restricted_days(data, restricted_days); + set_saflok_mfc_raw_interval(data, raw_interval); + set_saflok_mfc_raw_card_creation_date(data, raw_card_creation_date); + set_saflok_mfc_property_id(data, property_id); + + // Parsing date time string from "YYYY-MM-DDTHH:mm" into `saflok_mfc_datetime_t` + static const char * fmt = "%4u-%2u-%2uT%2u:%2u"; + + if (true) { + saflok_mfc_datetime_t creation_dt = {0}; + unsigned int year, month, day, hour, minute; + if (sscanf(dt, fmt, &year, &month, &day, &hour, &minute) == 5) { + if (month >= 1 && month <= 12 && day >= 1 && day <= 31 && hour <= 23 && minute <= 59) { + creation_dt.year = year; + creation_dt.month = (uint8_t)month; + creation_dt.day = (uint8_t)day; + creation_dt.hour = (uint8_t)hour; + creation_dt.minute = (uint8_t)minute; + set_saflok_mfc_card_creation_datetime(data, &creation_dt); + } + } + } + if (true) { + saflok_mfc_datetime_t expiration_dt = {0}; + unsigned int exp_year, exp_month, exp_day, exp_hour, exp_minute; + if (sscanf(dt_expiration, fmt, &exp_year, &exp_month, &exp_day, &exp_hour, &exp_minute) == 5) { + if (exp_month >= 1 && exp_month <= 12 && exp_day >= 1 && exp_day <= 31 && exp_hour <= 23 && exp_minute <= 59) { + expiration_dt.year = (uint16_t)exp_year; + expiration_dt.month = (uint8_t)exp_month; + expiration_dt.day = (uint8_t)exp_day; + expiration_dt.hour = (uint8_t)exp_hour; + expiration_dt.minute = (uint8_t)exp_minute; + set_saflok_mfc_card_expiration_datetime(data, &expiration_dt); + } + } + } + + uint8_t checksum = calculated_saflok_checksum(data); + set_bitfield(data, 128, 8, checksum); } +// TODO: make clearer how many bytes secdata must minimally point to, +// perhaps by creating a struct to avoid having to pass a length parameter. static int saflok_read_sector(int sector, uint8_t *secdata) { clearCommandBuffer(); SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); @@ -474,15 +1188,18 @@ static int CmdHFSaflokRead(const char *Cmd) { return PM3_EFAILED; } - uint8_t saflokdata[17]; - saflok_read_sector(0, secdata); - saflok_decrypt(secdata + 16, 17, saflokdata); + saflok_read_sector(0, secdata); // 64 bytes + + const saflok_mfc_data_t * encrypted = (const saflok_mfc_data_t *)(secdata + 16); + saflok_mfc_data_t decrypted = {0}; + saflok_decrypt(encrypted, &decrypted); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Card Information")); - PrintAndLogEx(SUCCESS, "Encrypted Data: " _GREEN_("%s"), bytes_to_hex(secdata + 16, 17)); + PrintAndLogEx(INFO, "Plaintext Data: %s", bytes_to_hex(decrypted.raw, sizeof(saflok_mfc_data_t))); + PrintAndLogEx(SUCCESS, "Encrypted Data: " _GREEN_("%s"), bytes_to_hex(encrypted->raw, sizeof(saflok_mfc_data_t))); - saflok_decode(saflokdata); + saflok_decode(&decrypted); return PM3_SUCCESS; } @@ -514,8 +1231,8 @@ static int CmdHFSaflokEncode(const char *Cmd) { CLIExecWithReturn(ctx, Cmd, argtable, true); - uint8_t rsaflokdata[17] = {0}; - uint8_t esaflokdata[17] = {0}; + saflok_mfc_data_t decrypted = {0}; + saflok_mfc_data_t encrypted = {0}; int slen = 0; char dt[100]; @@ -525,27 +1242,27 @@ static int CmdHFSaflokEncode(const char *Cmd) { CLIParamStrToBuf(arg_get_str(ctx, 10), (uint8_t *)dt_e, 100, &slen); - saflok_encode(rsaflokdata, - arg_get_u32_def(ctx, 1, 0), - arg_get_u32_def(ctx, 2, 0), - arg_get_u32_def(ctx, 3, 0), - arg_get_u32_def(ctx, 4, 0), - arg_get_u32_def(ctx, 5, 0), - arg_get_u32_def(ctx, 6, 0), - arg_get_u32_def(ctx, 7, 0), - arg_get_u32_def(ctx, 8, 0), - arg_get_u32_def(ctx, 9, 0), + saflok_encode(&decrypted, + arg_get_u32_def(ctx, 1, 0), // card_level + arg_get_u32_def(ctx, 2, 0), // card_type + arg_get_u32_def(ctx, 3, 0), // card_id + arg_get_u32_def(ctx, 4, 0), // opening_key + arg_get_u32_def(ctx, 5, 0), // lock_id + arg_get_u32_def(ctx, 6, 0), // pass_num + arg_get_u32_def(ctx, 7, 0), // seq_combo + arg_get_u32_def(ctx, 8, 0), // deadbolt + arg_get_u32_def(ctx, 9, 0), // days 0, 0, - arg_get_u32_def(ctx, 12, 0), + arg_get_u32_def(ctx, 12, 0), // property_id dt_e, dt); - - saflok_encrypt(rsaflokdata, 17, esaflokdata); + saflok_encrypt(&decrypted, &encrypted); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Encoded Card Data")); - PrintAndLogEx(SUCCESS, "Encrypted Data: " _GREEN_("%s"), bytes_to_hex(esaflokdata, 17)); + PrintAndLogEx(INFO, "Plaintext Data: %s", bytes_to_hex(decrypted.raw, sizeof(saflok_mfc_data_t))); + PrintAndLogEx(SUCCESS, "Encrypted Data: " _GREEN_("%s"), bytes_to_hex(encrypted.raw, sizeof(saflok_mfc_data_t))); CLIParserFree(ctx); @@ -556,7 +1273,7 @@ static int CmdHFSaflokDecode(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf saflok decode", "Decode saflok data", - "hf saflok decode"); + "hf saflok decode -d "); void *argtable[] = { arg_param_begin, @@ -565,10 +1282,10 @@ static int CmdHFSaflokDecode(const char *Cmd) { }; CLIExecWithReturn(ctx, Cmd, argtable, true); - uint8_t saflokdata[17]; - uint8_t dsaflokdata[17]; + saflok_mfc_data_t encrypted; + saflok_mfc_data_t decrypted; int dlen; - CLIGetHexWithReturn(ctx, 1, saflokdata, &dlen); + CLIGetHexWithReturn(ctx, 1, encrypted.raw, &dlen); CLIParserFree(ctx); @@ -577,8 +1294,12 @@ static int CmdHFSaflokDecode(const char *Cmd) { return PM3_EINVARG; } - saflok_decrypt(saflokdata, 17, dsaflokdata); - saflok_decode(dsaflokdata); + saflok_decrypt(&encrypted, &decrypted); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Decoded Card Data")); + PrintAndLogEx(INFO, "Plaintext Data: %s", bytes_to_hex(decrypted.raw, sizeof(saflok_mfc_data_t))); + PrintAndLogEx(INFO, "Encrypted Data: %s", bytes_to_hex(encrypted.raw, sizeof(saflok_mfc_data_t))); + saflok_decode(&decrypted); return PM3_SUCCESS; } @@ -612,54 +1333,55 @@ static int CmdHFSaflokModify(const char *Cmd) { CLIExecWithReturn(ctx, Cmd, argtable, true); - uint8_t user_saflokdata[17]; - uint8_t rsaflokdata[17]; - uint8_t esaflokdata[17] = {0}; + saflok_mfc_data_t encrypted; + saflok_mfc_data_t decrypted; + saflok_mfc_data_t reencrypted = {0}; int dlen; - CLIGetHexWithReturn(ctx, 13, user_saflokdata, &dlen); + CLIGetHexWithReturn(ctx, 13, encrypted.raw, &dlen); if (dlen != 17) { PrintAndLogEx(WARNING, "block data must include 17 HEX bytes. Got %i", dlen); return PM3_EINVARG; } - saflok_decrypt(user_saflokdata, 17, rsaflokdata); + saflok_decrypt(&encrypted, &decrypted); + PrintAndLogEx(INFO, "Plaintext Data: %s", bytes_to_hex(decrypted.raw, sizeof(saflok_mfc_data_t))); - uint32_t card_level = extract_bits(rsaflokdata, 0, 4); + uint32_t card_level = get_saflok_mfc_card_level(&decrypted); card_level = arg_get_u32_def(ctx, 1, card_level); - uint32_t card_type = extract_bits(rsaflokdata, 4, 4); + uint32_t card_type = get_saflok_mfc_card_type(&decrypted); card_type = arg_get_u32_def(ctx, 2, card_type); - uint32_t card_id = extract_bits(rsaflokdata, 8, 8); + uint32_t card_id = get_saflok_mfc_card_id(&decrypted); card_id = arg_get_u32_def(ctx, 3, card_id); - uint32_t opening_key = extract_bits(rsaflokdata, 16, 2); + uint32_t opening_key = get_saflok_mfc_opening_key(&decrypted); opening_key = arg_get_u32_def(ctx, 4, opening_key); - uint32_t lock_id = extract_bits(rsaflokdata, 18, 14); + uint32_t lock_id = get_saflok_mfc_lock_id(&decrypted); lock_id = arg_get_u32_def(ctx, 5, lock_id); - uint32_t pass_number = extract_bits(rsaflokdata, 32, 12); + uint32_t pass_number = get_saflok_mfc_pass_number(&decrypted); pass_number = arg_get_u32_def(ctx, 6, pass_number); - uint32_t sequence_and_combination = extract_bits(rsaflokdata, 44, 12); + uint32_t sequence_and_combination = get_saflok_mfc_sequence_and_combination(&decrypted); sequence_and_combination = arg_get_u32_def(ctx, 7, sequence_and_combination); - uint32_t deadbolt_override = extract_bits(rsaflokdata, 56, 1); + uint32_t deadbolt_override = get_saflok_mfc_deadbolt_override(&decrypted); deadbolt_override = arg_get_u32_def(ctx, 8, deadbolt_override); - uint32_t restricted_days = extract_bits(rsaflokdata, 57, 7); + uint32_t restricted_days = get_saflok_mfc_restricted_days(&decrypted); restricted_days = arg_get_u32_def(ctx, 9, restricted_days); - uint32_t expire_date = extract_bits(rsaflokdata, 64, 24); + uint32_t interval = get_saflok_mfc_raw_interval(&decrypted); //expire_date = arg_get_u32_def(ctx, 10, expire_date); - uint32_t card_creation_date = extract_bits(rsaflokdata, 88, 28); + uint32_t raw_card_creation_date = get_saflok_mfc_raw_card_creation_date(&decrypted); //card_creation_date = arg_get_u32_def(ctx, 11, card_creation_date); - uint32_t property_id = extract_bits(rsaflokdata, 116, 12); + uint32_t property_id = get_saflok_mfc_property_id(&decrypted); property_id = arg_get_u32_def(ctx, 12, property_id); int slen = 0; @@ -670,7 +1392,7 @@ static int CmdHFSaflokModify(const char *Cmd) { char dt_e[100]; CLIParamStrToBuf(arg_get_str(ctx, 10), (uint8_t *)dt_e, 100, &slen2); - saflok_encode(rsaflokdata, + saflok_encode(&decrypted, card_level, card_type, card_id, @@ -680,18 +1402,17 @@ static int CmdHFSaflokModify(const char *Cmd) { sequence_and_combination, deadbolt_override, restricted_days, - expire_date, - card_creation_date, + interval, + raw_card_creation_date, property_id, dt_e, dt); - - - saflok_encrypt(rsaflokdata, 17, esaflokdata); + saflok_encrypt(&decrypted, &reencrypted); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Modified Card Data")); - PrintAndLogEx(SUCCESS, "Encrypted Data: " _GREEN_("%s"), bytes_to_hex(esaflokdata, 17)); + PrintAndLogEx(INFO, "Plaintext Data: %s", bytes_to_hex(decrypted.raw, sizeof(saflok_mfc_data_t))); + PrintAndLogEx(SUCCESS, "Encrypted Data: " _GREEN_("%s"), bytes_to_hex(reencrypted.raw, sizeof(saflok_mfc_data_t))); CLIParserFree(ctx); @@ -713,9 +1434,10 @@ static int CmdHFSaflokEncrypt(const char *Cmd) { CLIExecWithReturn(ctx, Cmd, argtable, true); - uint8_t raw[17], encrypted[17]; + saflok_mfc_data_t raw = {0}; + saflok_mfc_data_t encrypted = {0}; int len; - CLIGetHexWithReturn(ctx, 1, raw, &len); + CLIGetHexWithReturn(ctx, 1, raw.raw, &len); if (len != 17) { PrintAndLogEx(WARNING, "Expected 17 bytes. Got %d.", len); @@ -723,8 +1445,8 @@ static int CmdHFSaflokEncrypt(const char *Cmd) { return PM3_EINVARG; } - saflok_encrypt(raw, 17, encrypted); - PrintAndLogEx(SUCCESS, "Encrypted: " _GREEN_("%s"), bytes_to_hex(encrypted, 17)); + saflok_encrypt(&raw, &encrypted); + PrintAndLogEx(SUCCESS, "Encrypted: " _GREEN_("%s"), bytes_to_hex(encrypted.raw, 17)); CLIParserFree(ctx); return PM3_SUCCESS; @@ -744,9 +1466,10 @@ static int CmdHFSaflokDecrypt(const char *Cmd) { CLIExecWithReturn(ctx, Cmd, argtable, true); - uint8_t encrypted[17], decrypted[17]; + saflok_mfc_data_t encrypted = {0}; + saflok_mfc_data_t decrypted = {0}; int len; - CLIGetHexWithReturn(ctx, 1, encrypted, &len); + CLIGetHexWithReturn(ctx, 1, encrypted.raw, &len); if (len != 17) { PrintAndLogEx(WARNING, "Expected 17 bytes. Got %d.", len); @@ -754,13 +1477,526 @@ static int CmdHFSaflokDecrypt(const char *Cmd) { return PM3_EINVARG; } - saflok_decrypt(encrypted, 17, decrypted); - PrintAndLogEx(SUCCESS, "Decrypted: " _GREEN_("%s"), bytes_to_hex(decrypted, 17)); + saflok_decrypt(&encrypted, &decrypted); + PrintAndLogEx(SUCCESS, "Decrypted: " _GREEN_("%s"), bytes_to_hex(decrypted.raw, 17)); CLIParserFree(ctx); return PM3_SUCCESS; } +static int CmdHFSaflokSelfTest(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf saflok selftest", + "Validate internal functionality of Saflok routines", + "hf saflok selftest"); + + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); + + int result = PM3_SUCCESS; + + PrintAndLogEx(WARNING, "Saflok self-test is non-exhaustive, 4-byte MFC only."); + // TODO: encode some sample cards using prior versions of the code. + // verify the resulting decoded data matches expectations, + // or investigate which one results in correct data. + + + + // get/set bitfield tests + if (true) { + int result_bitfield = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (size_t start_bit = 0; start_bit < ARRAYLEN(testdata.raw) * 8; start_bit += 3) { + for (size_t num_bits = 1; num_bits <= 32 && (start_bit + num_bits) <= (ARRAYLEN(testdata.raw) * 8); ++num_bits) { + uint64_t max_test_value = (num_bits == 32) ? 0xFFFFFFFFu : ((1ULL << num_bits) - 1); + for (uint64_t iter = 0u; iter <= max_test_value; iter += (max_test_value / 1024) + 1) { + uint32_t value_set = (uint32_t)iter; + bool success = set_bitfield(&testdata, start_bit, num_bits, value_set); + if (!success) { + PrintAndLogEx(FAILED, "set_bitfield failed for start_bit %zu, num_bits %zu, test_value " PRIx32, start_bit, num_bits, value_set); + result_bitfield = PM3_EFAILED; + } + uint32_t x = get_bitfield(&testdata, start_bit, num_bits); + if (x != value_set) { + PrintAndLogEx(FAILED, "get/set_bitfield failed for start_bit %zu, num_bits %zu, test_value " PRIx32 ": got " PRIx32", expected " PRIx32, start_bit, num_bits, value_set, x, value_set); + result_bitfield = PM3_EFAILED; + } + // Clear the field for the next test + memset(testdata.raw, 0, sizeof(testdata.raw)); + } + } + } + if (result_bitfield == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_bitfield passed"); + } else { + result = PM3_EFAILED; + } + } + // card_level field is 4 bits + if (true) { + int result_card_level = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (uint8_t i = 0; i < 16; ++i) { + bool success = set_saflok_mfc_card_level(&testdata, i); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_card_level failed for value %u", i); + result_card_level = PM3_EFAILED; + } else { + uint8_t x = get_saflok_mfc_card_level(&testdata); + if (x != i) { + PrintAndLogEx(FAILED, "get_saflok_mfc_card_level returned %u, expected %u", x, i); + result_card_level = PM3_EFAILED; + } + } + } + if (result_card_level == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_card_level passed"); + } else { + result = PM3_EFAILED; + } + + } + // card_type field is 4 bits + if (true) { + int result_card_type = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (uint8_t i = 0; i < 16; ++i) { + bool success = set_saflok_mfc_card_type(&testdata, i); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_card_type failed for value %u", i); + result_card_type = PM3_EFAILED; + } else { + uint8_t x = get_saflok_mfc_card_type(&testdata); + if (x != i) { + PrintAndLogEx(FAILED, "get_saflok_mfc_card_type returned %u, expected %u", x, i); + result_card_type = PM3_EFAILED; + } + } + } + if (result_card_type == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_card_type passed"); + } else { + result = PM3_EFAILED; + } + } + // card_id field is 8 bits + if (true) { + int result_card_id = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (uint16_t i = 0; i < 256; ++i) { + bool success = set_saflok_mfc_card_id(&testdata, i); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_card_type failed for value %u", i); + result_card_id = PM3_EFAILED; + } else { + uint8_t x = get_saflok_mfc_card_id(&testdata); + if (x != i) { + PrintAndLogEx(FAILED, "get_saflok_mfc_card_id returned %u, expected %u", x, i); + result_card_id = PM3_EFAILED; + } + } + } + if (result_card_id == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_card_id passed"); + } else { + result = PM3_EFAILED; + } + } + // opening_key field is 2 bits + if (true) { + int result_opening_key = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (uint8_t i = 0; i < 4; ++i) { + bool success = set_saflok_mfc_opening_key(&testdata, i); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_opening_key failed for value %u", i); + result_opening_key = PM3_EFAILED; + } else { + uint8_t x = get_saflok_mfc_opening_key(&testdata); + if (x != i) { + PrintAndLogEx(FAILED, "get_saflok_mfc_opening_key returned %u, expected %u", x, i); + result_opening_key = PM3_EFAILED; + } + } + } + if (result_opening_key == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_opening_key passed"); + } else { + result = PM3_EFAILED; + } + } + // lock_id field is 14 bits + if (true) { + int result_lock_id = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (uint16_t i = 0; i < 16384; i += 11) { // step by prime to test various bits + bool success = set_saflok_mfc_lock_id(&testdata, i); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_lock_id failed for value %u", i); + result_lock_id = PM3_EFAILED; + } else { + uint16_t x = get_saflok_mfc_lock_id(&testdata); + if (x != i) { + PrintAndLogEx(FAILED, "get_saflok_mfc_lock_id returned %u, expected %u", x, i); + result_lock_id = PM3_EFAILED; + } + } + } + if (result_lock_id == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_lock_id passed"); + } else { + result = PM3_EFAILED; + } + } + // pass_number field is 12 bits + if (true) { + int result_pass_number = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (uint16_t i = 0; i < 4096; i += 7) { // step by prime to test various bits + bool success = set_saflok_mfc_pass_number(&testdata, i); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_pass_number failed for value %u", i); + result_pass_number = PM3_EFAILED; + } else { + uint16_t x = get_saflok_mfc_pass_number(&testdata); + if (x != i) { + PrintAndLogEx(FAILED, "get_saflok_mfc_pass_number returned %u, expected %u", x, i); + result_pass_number = PM3_EFAILED; + } + } + } + if (result_pass_number == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_pass_number passed"); + } else { + result = PM3_EFAILED; + } + } + // sequence_and_combination field is 12 bits + if (true) { + int result_seq_combo = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (uint16_t i = 0; i < 4096; i += 5) { // step by prime to test various bits + bool success = set_saflok_mfc_sequence_and_combination(&testdata, i); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_sequence_and_combination failed for value %u", i); + result_seq_combo = PM3_EFAILED; + } else { + uint16_t x = get_saflok_mfc_sequence_and_combination(&testdata); + if (x != i) { + PrintAndLogEx(FAILED, "get_saflok_mfc_sequence_and_combination returned %u, expected %u", x, i); + result_seq_combo = PM3_EFAILED; + } + } + } + if (result_seq_combo == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_sequence_and_combination passed"); + } else { + result = PM3_EFAILED; + } + } + // deadbolt_override field is 1 bits + if (true) { + int result_deadbolt = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (uint8_t i = 0; i < 2; ++i) { + bool success = set_saflok_mfc_deadbolt_override(&testdata, i); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_deadbolt_override failed for value %u", i); + result_deadbolt = PM3_EFAILED; + } else { + uint8_t x = get_saflok_mfc_deadbolt_override(&testdata); + if (x != i) { + PrintAndLogEx(FAILED, "get_saflok_mfc_deadbolt_override returned %u, expected %u", x, i); + result_deadbolt = PM3_EFAILED; + } + } + } + if (result_deadbolt == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_deadbolt_override passed"); + } else { + result = PM3_EFAILED; + } + } + // restricted_days field is 7 bits + if (true) { + int result_restricted_days = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (uint8_t i = 0; i < 128; ++i) { + bool success = set_saflok_mfc_restricted_days(&testdata, i); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_restricted_days failed for value %u", i); + result_restricted_days = PM3_EFAILED; + } else { + uint8_t x = get_saflok_mfc_restricted_days(&testdata); + if (x != i) { + PrintAndLogEx(FAILED, "get_saflok_mfc_restricted_days returned %u, expected %u", x, i); + result_restricted_days = PM3_EFAILED; + } + } + } + if (result_restricted_days == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_restricted_days passed"); + } else { + result = PM3_EFAILED; + } + } + // property_id field is 12 bits + if (true) { + int result_property_id = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (uint16_t i = 0; i < 4096; i += 13) { // step by prime to test various bits + bool success = set_saflok_mfc_property_id(&testdata, i); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_property_id failed for value %u", i); + result_property_id = PM3_EFAILED; + } else { + uint16_t x = get_saflok_mfc_property_id(&testdata); + if (x != i) { + PrintAndLogEx(FAILED, "get_saflok_mfc_property_id returned %u, expected %u", x, i); + result_property_id = PM3_EFAILED; + } + } + } + if (result_property_id == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_property_id passed"); + } else { + result = PM3_EFAILED; + } + } + if (true) { + int result_checksum = PM3_SUCCESS; + saflok_mfc_data_t testdata = {0}; + for (size_t i = 0; i < 0x100; ++i) { + uint8_t value = (uint8_t)i; + bool success = set_saflok_mfc_checksum(&testdata, value); + if (!success) { + PrintAndLogEx(FAILED, "set_saflok_mfc_checksum failed for value %02x", value); + result_checksum = PM3_EFAILED; + } else { + uint8_t x = get_saflok_mfc_checksum(&testdata); + if (x != value) { + PrintAndLogEx(FAILED, "get_saflok_mfc_checksum returned %02x, expected %02x", x, value); + result_checksum = PM3_EFAILED; + } + } + } + if (result_checksum == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "get/set_saflok_mfc_checksum passed"); + } else { + result = PM3_EFAILED; + } + } + + /* + // raw_interval field is 20 bits -- special set processing needed + // raw_expire_date field is 24 bits -- special set processing needed + // raw_card_creation_date field is 28 bits -- special set processing needed + if (true) { + [ ] get_saflok_mfc_raw_interval_date + [ ] set_saflok_mfc_raw_interval_date + } + if (true) { + [ ] get_saflok_mfc_raw_card_creation_date + [ ] set_saflok_mfc_raw_card_creation_date + } + if (true) { + [ ] is_saflok_mfc_datetime_valid + [ ] add_offset + [ ] get_datetime_offset + } + if (true) { + [ ] _get_saflok_mfc_card_creation_year_impl + [ ] _set_saflok_mfc_card_creation_year_impl + [ ] _get_saflok_mfc_card_creation_month_impl + [ ] _set_saflok_mfc_card_creation_month_impl + [ ] _get_saflok_mfc_card_creation_day_impl + [ ] _set_saflok_mfc_card_creation_day_impl + [ ] _get_saflok_mfc_card_creation_hour_impl + [ ] _set_saflok_mfc_card_creation_hour_impl + [ ] _get_saflok_mfc_card_creation_minute_impl + [ ] _set_saflok_mfc_card_creation_minute_impl + [ ] get_saflok_mfc_card_creation_datetime + [ ] set_saflok_mfc_card_creation_datetime + } + if (true) { + // NOTE: these require card creation date to be set first + [ ] _get_saflok_mfc_interval_years_impl + [ ] _set_saflok_mfc_interval_years_impl + [ ] _get_saflok_mfc_interval_months_impl + [ ] _set_saflok_mfc_interval_months_impl + [ ] _get_saflok_mfc_interval_days_impl + [ ] _set_saflok_mfc_interval_days_impl + [ ] _get_saflok_mfc_interval_hours_impl + [ ] _set_saflok_mfc_interval_hours_impl + [ ] _get_saflok_mfc_interval_minutes_impl + [ ] _set_saflok_mfc_interval_minutes_impl + [ ] get_saflok_mfc_interval + [ ] set_saflok_mfc_interval + [ ] get_saflok_mfc_card_expiration_datetime + [ ] set_saflok_mfc_card_expiration_datetime + } + */ + if (true) { + int result_test_cases = PM3_SUCCESS; + + for (size_t i = 0; i < ARRAYLEN(saflok_test_cases); ++i) { + const saflok_test_case_t * tc = &saflok_test_cases[i]; + + // encoded -> decoded -> encoded + saflok_mfc_data_t tmp = {0}; + saflok_encrypt(&tc->decoded, &tmp); + if (memcmp(tmp.raw, tc->encoded.raw, sizeof(saflok_mfc_data_t)) != 0) { + PrintAndLogEx(FAILED, "Test case %zu: encryption did not match expected data.", i); + PrintAndLogEx(DEBUG, " raw result: %s", bytes_to_hex(tmp.raw, sizeof(saflok_mfc_data_t))); + PrintAndLogEx(DEBUG, " expected result: %s", bytes_to_hex(tc->encoded.raw, sizeof(saflok_mfc_data_t))); + result_test_cases = PM3_EFAILED; + continue; + } + saflok_decrypt(&tc->encoded, &tmp); + if (memcmp(tmp.raw, tc->decoded.raw, sizeof(saflok_mfc_data_t)) != 0) { + PrintAndLogEx(FAILED, "Test case %zu: decryption did not match expected data.", i); + PrintAndLogEx(DEBUG, " raw result: %s", bytes_to_hex(tmp.raw, sizeof(saflok_mfc_data_t))); + PrintAndLogEx(DEBUG, " expected result: %s", bytes_to_hex(tc->decoded.raw, sizeof(saflok_mfc_data_t))); + result_test_cases = PM3_EFAILED; + continue; + } + saflok_mfc_datetime_t tmp_dt = get_saflok_mfc_card_creation_datetime(&tc->decoded); + if (memcmp(&tmp_dt, &tc->card_creation_datetime, sizeof(saflok_mfc_datetime_t)) != 0) { + PrintAndLogEx(FAILED, "Test case %zu: card creation datetime did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %4u-%02u-%02u %02u:%02u", + tmp_dt.year, tmp_dt.month, tmp_dt.day, + tmp_dt.hour, tmp_dt.minute); + PrintAndLogEx(DEBUG, " expected result: %4u-%02u-%02u %02u:%02u", + tc->card_creation_datetime.year, tc->card_creation_datetime.month, tc->card_creation_datetime.day, + tc->card_creation_datetime.hour, tc->card_creation_datetime.minute); + result_test_cases = PM3_EFAILED; + continue; + } + tmp_dt = get_saflok_mfc_card_expiration_datetime(&tc->decoded); + if (memcmp(&tmp_dt, &tc->card_expiration_datetime, sizeof(saflok_mfc_datetime_t)) != 0) { + PrintAndLogEx(FAILED, "Test case %zu: card expiration datetime did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %4u-%02u-%02u %02u:%02u", + tmp_dt.year, tmp_dt.month, tmp_dt.day, + tmp_dt.hour, tmp_dt.minute); + PrintAndLogEx(DEBUG, " expected result: %4u-%02u-%02u %02u:%02u", + tc->card_expiration_datetime.year, tc->card_expiration_datetime.month, tc->card_expiration_datetime.day, + tc->card_expiration_datetime.hour, tc->card_expiration_datetime.minute); + result_test_cases = PM3_EFAILED; + continue; + } + saflok_mfc_datetime_offset_t tmp_offset = get_saflok_mfc_interval(&tc->decoded); + if (memcmp(&tmp_offset, &tc->card_interval, sizeof(saflok_mfc_datetime_offset_t)) != 0) { + PrintAndLogEx(FAILED, "Test case %zu: card interval did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u years, %u months, %u days, %u hours, %u minutes", + tmp_offset.years, tmp_offset.months, tmp_offset.days, + tmp_offset.hours, tmp_offset.minutes); + PrintAndLogEx(DEBUG, " expected result: %u years, %u months, %u days, %u hours, %u minutes", + tc->card_interval.years, tc->card_interval.months, tc->card_interval.days, + tc->card_interval.hours, tc->card_interval.minutes); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_lock_id(&tc->decoded) != tc->lock_id) { + PrintAndLogEx(FAILED, "Test case %zu: lock_id did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_lock_id(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->lock_id); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_pass_number(&tc->decoded) != tc->pass_number) { + PrintAndLogEx(FAILED, "Test case %zu: pass_number did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_pass_number(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->pass_number); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_sequence_and_combination(&tc->decoded) != tc->sequence_and_combination) { + PrintAndLogEx(FAILED, "Test case %zu: sequence_and_combination did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_sequence_and_combination(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->sequence_and_combination); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_property_id(&tc->decoded) != tc->property_id) { + PrintAndLogEx(FAILED, "Test case %zu: property_id did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_property_id(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->property_id); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_card_level(&tc->decoded) != tc->card_level) { + PrintAndLogEx(FAILED, "Test case %zu: card_level did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_card_level(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->card_level); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_card_type(&tc->decoded) != tc->card_type) { + PrintAndLogEx(FAILED, "Test case %zu: card_type did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_card_type(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->card_type); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_card_id(&tc->decoded) != tc->card_id) { + PrintAndLogEx(FAILED, "Test case %zu: card_id did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_card_id(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->card_id); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_opening_key(&tc->decoded) != tc->opening_key) { + PrintAndLogEx(FAILED, "Test case %zu: opening_key did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_opening_key(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->opening_key); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_restricted_days(&tc->decoded) != tc->restricted_days) { + PrintAndLogEx(FAILED, "Test case %zu: restricted_days did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_restricted_days(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->restricted_days); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_deadbolt_override(&tc->decoded) != tc->deadbolt_override) { + PrintAndLogEx(FAILED, "Test case %zu: deadbolt_override did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_deadbolt_override(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->deadbolt_override); + result_test_cases = PM3_EFAILED; + continue; + } + if (get_saflok_mfc_checksum(&tc->decoded) != tc->checksum) { + PrintAndLogEx(FAILED, "Test case %zu: stored checksum did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", get_saflok_mfc_checksum(&tc->decoded)); + PrintAndLogEx(DEBUG, " expected result: %u", tc->checksum); + result_test_cases = PM3_EFAILED; + continue; + } + uint8_t checksum = calculated_saflok_checksum(&tc->decoded); + if (checksum != tc->checksum) { + PrintAndLogEx(FAILED, "Test case %zu: calculated checksum did not match expected data.", i); + PrintAndLogEx(DEBUG, " api result: %u", checksum); + PrintAndLogEx(DEBUG, " expected result: %u", tc->checksum); + result_test_cases = PM3_EFAILED; + continue; + } + } + if (result_test_cases == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "All predefined test cases passed."); + } else { + result = PM3_EFAILED; + } + } + + return result; +} + + static int CmdHFSaflokChecksum(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf saflok cksum", @@ -775,9 +2011,9 @@ static int CmdHFSaflokChecksum(const char *Cmd) { CLIExecWithReturn(ctx, Cmd, argtable, true); - uint8_t data[17]; + saflok_mfc_data_t data = {0}; int len; - CLIGetHexWithReturn(ctx, 1, data, &len); + CLIGetHexWithReturn(ctx, 1, data.raw, &len); if (len != 16) { PrintAndLogEx(WARNING, "Expected 16 bytes. Got %d.", len); @@ -785,10 +2021,10 @@ static int CmdHFSaflokChecksum(const char *Cmd) { return PM3_EINVARG; } - data[16] = saflok_checksum(data, 16); + data.raw[16] = calculated_saflok_checksum(&data); - PrintAndLogEx(SUCCESS, "Block + checksum: " _GREEN_("%s"), bytes_to_hex(data, 17)); - PrintAndLogEx(SUCCESS, "Checksum byte: " _GREEN_("0x%02X"), data[16]); + PrintAndLogEx(SUCCESS, "Block + checksum: " _GREEN_("%s"), bytes_to_hex(data.raw, 17)); + PrintAndLogEx(SUCCESS, "Checksum byte: " _GREEN_("0x%02X"), data.raw[16]); CLIParserFree(ctx); return PM3_SUCCESS; @@ -808,9 +2044,9 @@ static int CmdHFSaflokProvision(const char *Cmd) { CLIExecWithReturn(ctx, Cmd, argtable, true); - uint8_t data[17]; + saflok_mfc_data_t data = {0}; int len; - CLIGetHexWithReturn(ctx, 1, data, &len); + CLIGetHexWithReturn(ctx, 1, data.raw, &len); if (len != 17) { PrintAndLogEx(WARNING, "Expected 17 bytes, got %d", len); @@ -818,6 +2054,7 @@ static int CmdHFSaflokProvision(const char *Cmd) { return PM3_EINVARG; } + // buffer size for UID is eight bytes ... even though saflok only uses first four bytes uint8_t uid[8]; int uid_len; if (mf_read_uid(uid, &uid_len, NULL) != PM3_SUCCESS || uid_len < 4) { @@ -826,34 +2063,38 @@ static int CmdHFSaflokProvision(const char *Cmd) { return PM3_ESOFT; } - uint8_t keyA[6]; - saflok_kdf(uid, keyA); - PrintAndLogEx(INFO, "Generated UID-derived key: " _GREEN_("%s"), bytes_to_hex(keyA, 6)); + _Static_assert(ARRAYLEN(uid) >= sizeof(saflok_mfc_uid_t), "UID array too small"); + const saflok_mfc_uid_t * saflok_uid = (const saflok_mfc_uid_t *)uid; + saflok_mfc_key_t keyA = {0}; + + saflok_kdf(saflok_uid, &keyA); + PrintAndLogEx(INFO, "Generated UID-derived key: " _GREEN_("%s"), bytes_to_hex(keyA.key, ARRAYLEN(keyA.key))); uint8_t all_F[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; uint8_t block1[16]; uint8_t block2[16] = {0}; - memcpy(block1, data, 16); - block2[0] = data[16]; + memcpy(block1, data.raw, 16); + block2[0] = data.raw[16]; block2[1] = 0x00; block2[2] = 0x04; block2[3] = 0x00; block2[4] = 0x01; - bool write_success = mf_write_block(1, 0, keyA, block1) == PM3_SUCCESS && - mf_write_block(2, 0, keyA, block2) == PM3_SUCCESS; + bool write_success = mf_write_block(1, 0, keyA.key, block1) == PM3_SUCCESS && + mf_write_block(2, 0, keyA.key, block2) == PM3_SUCCESS; uint8_t trailer0[16] = {0}; uint8_t set_keys = 0; if (!write_success) { PrintAndLogEx(WARNING, "Initial write failed. Attempting to set sector 0 keys..."); - memcpy(trailer0, keyA, 6); + _Static_assert(sizeof(saflok_mfc_key_t) == 6u, "saflok_mfc_key_t size changed?"); + memcpy(trailer0, &keyA, sizeof(saflok_mfc_key_t)); trailer0[6] = 0xFF; trailer0[7] = 0x07; trailer0[8] = 0x80; trailer0[9] = 0x69; - memcpy(trailer0 + 10, all_F, 6); + memset(trailer0 + 10, 0xFF, sizeof(saflok_mfc_key_t)); if (mf_write_block(3, 1, all_F, trailer0) != PM3_SUCCESS) { PrintAndLogEx(WARNING, "Failed to set key in sector 0. Try wiping the card first."); @@ -861,8 +2102,8 @@ static int CmdHFSaflokProvision(const char *Cmd) { return PM3_ESOFT; } - write_success = mf_write_block(1, 0, keyA, block1) == PM3_SUCCESS && - mf_write_block(2, 0, keyA, block2) == PM3_SUCCESS; + write_success = mf_write_block(1, 0, keyA.key, block1) == PM3_SUCCESS && + mf_write_block(2, 0, keyA.key, block2) == PM3_SUCCESS; if (!write_success) { PrintAndLogEx(WARNING, "Write still failed after setting keys."); CLIParserFree(ctx); @@ -911,24 +2152,26 @@ static int CmdHFSaflokInterrogate(const char *Cmd) { if (mf_read_uid(uid, &uid_len, NULL) != PM3_SUCCESS || uid_len < 4) { PrintAndLogEx(WARNING, "Failed to read UID."); - CLIParserFree(ctx); return PM3_ESOFT; } - uint8_t key[6]; - saflok_kdf(uid, key); + _Static_assert(ARRAYLEN(uid) >= sizeof(saflok_mfc_uid_t), "UID array too small"); + const saflok_mfc_uid_t * saflok_uid = (const saflok_mfc_uid_t *)uid; + + saflok_mfc_key_t key = {0}; + saflok_kdf(saflok_uid, &key); uint8_t block2[16]; - if (mf_read_block(2, 0, key, block2) != PM3_SUCCESS) { + if (mf_read_block(2, 0, key.key, block2) != PM3_SUCCESS) { PrintAndLogEx(WARNING, "Failed to read block 2 with derived key."); - CLIParserFree(ctx); return PM3_ESOFT; } uint8_t control_byte = block2[5]; uint8_t subblock_stop = (control_byte >> 3); if (subblock_stop == 0) { - CLIParserFree(ctx); + // same message as below for total_bytes == 0 + PrintAndLogEx(SUCCESS, "Card has no variable keys"); return PM3_EINVARG; } @@ -945,7 +2188,7 @@ static int CmdHFSaflokInterrogate(const char *Cmd) { continue; } - if (mf_read_block(current_block, 0, key, buffer + total_bytes) != PM3_SUCCESS) { + if (mf_read_block(current_block, 0, key.key, buffer + total_bytes) != PM3_SUCCESS) { PrintAndLogEx(WARNING, "Failed to read block %d", current_block); break; } @@ -974,7 +2217,6 @@ static int CmdHFSaflokInterrogate(const char *Cmd) { cursor += 6; } - CLIParserFree(ctx); return PM3_SUCCESS; } @@ -989,6 +2231,7 @@ static command_t CommandTable[] = { {"decrypt", CmdHFSaflokDecrypt, AlwaysAvailable, "Decrypt 17-byte encrypted block"}, {"interrogate", CmdHFSaflokInterrogate, IfPm3NfcBarcode, "Interrogate saflok card"}, {"cksum", CmdHFSaflokChecksum, IfPm3NfcBarcode, "Generate checksum for data block"}, + {"selftest", CmdHFSaflokSelfTest, AlwaysAvailable, "Run self-test"}, {NULL, NULL, NULL, NULL} }; diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 2c894eced1..cdfdfc8ab6 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -359,7 +359,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf mf fchk" }, { 1, "hf mf decrypt" }, { 0, "hf mf supercard" }, - { 1, "hf mf bambukeys" }, + { 1, "hf mf keygen" }, { 0, "hf mf auth4" }, { 1, "hf mf acl" }, { 0, "hf mf dump" }, @@ -511,6 +511,7 @@ const static vocabulary_t vocabulary[] = { { 1, "hf saflok decrypt" }, { 0, "hf saflok interrogate" }, { 0, "hf saflok cksum" }, + { 1, "hf saflok selftest" }, { 1, "hf seos help" }, { 1, "hf seos list" }, { 0, "hf seos sam" }, diff --git a/doc/commands.json b/doc/commands.json index ec0c14f48d..07263b760f 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -3256,7 +3256,7 @@ }, "hf help": { "command": "hf help", - "description": "-------- ----------------------- High Frequency ----------------------- 14a { ISO14443A RFIDs... } 14b { ISO14443B RFIDs... } 15 { ISO15693 RFIDs... } cipurse { Cipurse transport Cards... } epa { German Identification Card... } emrtd { Machine Readable Travel Document... } felica { ISO18092 / FeliCa RFIDs... } fido { FIDO and FIDO2 authenticators... } fudan { Fudan RFIDs... } gallagher { Gallagher DESFire RFIDs... } iclass { ICLASS RFIDs... } ict { ICT MFC/DESfire RFIDs... } jooki { Jooki RFIDs... } ksx6924 { KS X 6924 (T-Money, Snapper+) RFIDs } legic { LEGIC RFIDs... } lto { LTO Cartridge Memory RFIDs... } mf { MIFARE RFIDs... } mfp { MIFARE Plus RFIDs... } mfu { MIFARE Ultralight RFIDs... } mfdes { MIFARE Desfire RFIDs... } ntag424 { NXP NTAG 4242 DNA RFIDs... } saflok { Saflok MFC RFIDs... } seos { SEOS RFIDs... } st25ta { ST25TA RFIDs... } tesla { TESLA Cards... } texkom { Texkom RFIDs... } thinfilm { Thinfilm RFIDs... } topaz { TOPAZ (NFC Type 1) RFIDs... } vas { Apple Value Added Service... } waveshare { Waveshare NFC ePaper... } xerox { Fuji/Xerox cartridge RFIDs... } ----------- --------------------- General --------------------- help This help list List protocol data in trace buffer search Search for known HF tags --------------------------------------------------------------------------------------- hf list available offline: yes Alias of `trace list -t raw` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "description": "-------- ----------------------- High Frequency ----------------------- 14a { ISO14443A RFIDs... } 14b { ISO14443B RFIDs... } 15 { ISO15693 RFIDs... } cipurse { Cipurse transport Cards... } epa { German Identification Card... } emrtd { Machine Readable Travel Document... } felica { ISO18092 / FeliCa RFIDs... } fido { FIDO and FIDO2 authenticators... } fudan { Fudan RFIDs... } gallagher { Gallagher DESFire RFIDs... } iclass { ICLASS RFIDs... } ict { ICT MFC/DESfire RFIDs... } jooki { Jooki RFIDs... } ksx6924 { KS X 6924 (T-Money, Snapper+) RFIDs } legic { LEGIC RFIDs... } lto { LTO Cartridge Memory RFIDs... } mf { MIFARE RFIDs... } mfp { MIFARE Plus RFIDs... } mfu { MIFARE Ultralight RFIDs... } mfdes { MIFARE Desfire RFIDs... } ntag424 { NXP NTAG 4242 DNA RFIDs... } saflok { Saflok MFC RFIDs... } seos { SEOS RFIDs... } st25ta { ST25TA RFIDs... } tesla { TESLA Cards... } texkom { Texkom RFIDs... } thinfilm { Thinfilm RFIDs... } topaz { TOPAZ (NFC Type 1) RFIDs... } vas { Apple Value Added Service... } xerox { Fuji/Xerox cartridge RFIDs... } ----------- --------------------- General --------------------- help This help list List protocol data in trace buffer search Search for known HF tags --------------------------------------------------------------------------------------- hf list available offline: yes Alias of `trace list -t raw` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "notes": [ "hf list --frame -> show frame delay times", "hf list -1 -> use trace buffer" @@ -7815,7 +7815,7 @@ }, "hf saflok help": { "command": "hf saflok help", - "description": "help This help encode Encode Saflok card data decode Decode Saflok card data modify Modify Saflok card data encrypt Encrypt 17-byte decrypted block decrypt Decrypt 17-byte encrypted block --------------------------------------------------------------------------------------- hf saflok read available offline: no Read Saflok card (MIFARE Classic only)", + "description": "help This help encode Encode Saflok card data decode Decode Saflok card data modify Modify Saflok card data encrypt Encrypt 17-byte decrypted block decrypt Decrypt 17-byte encrypted block selftest Run self-test --------------------------------------------------------------------------------------- hf saflok read available offline: no Read Saflok card (MIFARE Classic only)", "notes": [ "hf saflok read" ], @@ -7875,6 +7875,18 @@ ], "usage": "hf saflok provision [-h] -d data" }, + "hf saflok selftest": { + "command": "hf saflok selftest", + "description": "Validate internal functionality of Saflok routines", + "notes": [ + "hf saflok selftest" + ], + "offline": true, + "options": [ + "-h, --help This help" + ], + "usage": "hf saflok selftest [-h]" + }, "hf search": { "command": "hf search", "description": "Will try to find a HF read out of the unknown tag. Continues to search for all different HF protocols.", @@ -8438,28 +8450,6 @@ ], "usage": "hf vas reader [-h@v] [--pid ] [-f ] [--url ]" }, - "hf waveshare help": { - "command": "hf waveshare help", - "description": "help This help load Load image file to Waveshare NFC ePaper --------------------------------------------------------------------------------------- hf waveshare load available offline: yes Load image file to Waveshare NFC ePaper", - "notes": [ - "hf waveshare load -f myfile -m 0 -> 2.13 inch e-paper ( 122, 250 )", - "hf waveshare load -f myfile -m 1 -> 2.9 inch e-paper ( 296, 128 )", - "hf waveshare load -f myfile -m 2 -> 4.2 inch e-paper ( 400, 300 )", - "hf waveshare load -f myfile -m 3 -> 7.5 inch e-paper ( 800, 480 )", - "hf waveshare load -f myfile -m 4 -> 2.7 inch e-paper ( 176, 276 )", - "hf waveshare load -f myfile -m 5 -> 2.13 inch e-paper B (with red) ( 104, 212 )", - "hf waveshare load -f myfile -m 6 -> 1.54 inch e-paper B (with red) ( 200, 200 )", - "hf waveshare load -f myfile -m 7 -> 7.5 inch e-paper HD ( 880, 528 )" - ], - "offline": true, - "options": [ - "-h, --help This help", - "-m model number [0 - 7] of your tag", - "-f, --file specify image to upload to tag", - "-s, --save save paletized version in file" - ], - "usage": "hf waveshare load [-h] -m -f [-s ]" - }, "hf xerox dump": { "command": "hf xerox dump", "description": "Dump all memory from a Fuji/Xerox tag ISO/IEC 14443 type B based communications", @@ -13701,6 +13691,6 @@ "metadata": { "commands_extracted": 785, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-11-10T17:48:24" + "extracted_on": "2025-11-15T03:41:46" } } diff --git a/doc/commands.md b/doc/commands.md index 9bf0f776a2..fde136f369 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -722,6 +722,7 @@ Check column "offline" for their availability. |`hf saflok decrypt `|Y |`Decrypt 17-byte encrypted block` |`hf saflok interrogate `|N |`Interrogate saflok card` |`hf saflok cksum `|N |`Generate checksum for data block` +|`hf saflok selftest `|Y |`Run self-test` ### hf seos @@ -819,16 +820,6 @@ Check column "offline" for their availability. |`hf vas decrypt `|Y |`Decrypt a previously captured VAS cryptogram` -### hf waveshare - - { Waveshare NFC ePaper... } - -|command |offline |description -|------- |------- |----------- -|`hf waveshare help `|Y |`This help` -|`hf waveshare load `|Y |`Load image file to Waveshare NFC ePaper` - - ### hf xerox { Fuji/Xerox cartridge RFIDs... }