From aae70014e07a74751ad6c4a45324b7e2d02aadaa Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Mon, 14 Sep 2026 12:21:47 +0200 Subject: [PATCH] - VIGIK service badge fields now show which ones the RSA signature actually covers --- CHANGELOG.md | 1 + client/src/parsers/parsevigik.c | 53 +++++++++++++-------------------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1d0e51bd..bf8bb7a75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Changed `hf mf view` - now show which VIGIK fields are RSA signed (@iceman1001) - Changed `hw status` - now reports EMULATOR memory (@iceman1001) - Changed `hf mf view` - Urmet Captiv cards now decode their number field and data regions (@iceman1001) - Changed `hf mf view --selftest` - now also checks the Hexact payload decoder, both ways (@iceman1001) diff --git a/client/src/parsers/parsevigik.c b/client/src/parsers/parsevigik.c index e7dc5d79b..f435956e4 100644 --- a/client/src/parsers/parsevigik.c +++ b/client/src/parsers/parsevigik.c @@ -32,6 +32,7 @@ #include "mbedtls/bignum.h" #define VIGIK_SIG_LEN 128 // RSA 1024, the signature on the card +#define VIGIK_SIGNED "( " _GREEN_("signed") " )" #define VIGIK_MSG_SLOTS 64 // message byte slots in an ISO 9796-1 block #define VIGIK_MSG_PAD 8 // leading zero bytes of the signed message @@ -62,7 +63,6 @@ const char *vigik_get_service(uint16_t service_code) { // ISO 9796-1 shadow permutation. Each message byte is stored next to its shadow, -// which is what gives the scheme its redundancy. static const uint8_t vigik_iso9796_pi[16] = { 0x0E, 0x03, 0x05, 0x08, 0x09, 0x04, 0x02, 0x0F, 0x00, 0x0D, 0x0B, 0x06, 0x07, 0x0A, 0x0C, 0x01 @@ -75,10 +75,6 @@ static uint8_t vigik_shadow(uint8_t b) { // Recover the message an ISO 9796-1 signature carries, checking the redundancy on // the way. `sig` is the signature as it sits on the card, `modulus` the public key // to try. On success msg holds msglen bytes. -// -// Rabin, so v = 2 and squaring the signature gives the block back. Which of the -// four candidates is the block is decided by the format itself: an ISO 9796-1 -// block always ends in the nibble 6. static int vigik_iso9796_recover(const uint8_t *sig, const char *modulus, uint8_t *msg, size_t *msglen) { uint8_t n[VIGIK_SIG_LEN] = {0}; @@ -158,9 +154,6 @@ static int vigik_iso9796_recover(const uint8_t *sig, const char *modulus, uint8_ return PM3_ESOFT; } - // Every byte pair has to be a message byte next to its shadow. Exactly three - // are allowed not to be: the leading pair carries the bit that keeps the block - // below n, one marks how long the message is, and the last holds the forced 6. size_t broken[4] = {0}; size_t brokencnt = 0; for (size_t i = 0; i < VIGIK_SIG_LEN; i += 2) { @@ -186,9 +179,6 @@ static int vigik_iso9796_recover(const uint8_t *sig, const char *modulus, uint8_ stream[i / 2] = f[i + 1]; } - // The message is repeated to fill the block, the first slot belonging to the - // truncated tail of the run before it. Where a second copy fits, it has to - // agree with the first. if (((1 + (2 * z)) <= VIGIK_MSG_SLOTS) && (memcmp(stream + 1, stream + 1 + z, z) != 0)) { return PM3_ESOFT; } @@ -198,26 +188,21 @@ static int vigik_iso9796_recover(const uint8_t *sig, const char *modulus, uint8_ return PM3_SUCCESS; } -// The five byte dates are year since 1900, then month, day, hour, minute. -// Print the raw bytes with the date spelled out beside them, and leave the -// decoded form off when the bytes cannot be a date at all. -static void vigik_print_date(const char *label, const uint8_t *v) { +static void vigik_print_date(const char *label, const uint8_t *v, const char *suffix) { bool sane = ((v[1] >= 1) && (v[1] <= 12) && (v[2] >= 1) && (v[2] <= 31) && (v[3] <= 23) && (v[4] <= 59)); if (sane) { - PrintAndLogEx(INFO, "%s %s ( " _YELLOW_("%04u-%02u-%02u %02u:%02u") " )", + PrintAndLogEx(INFO, "%s %s ( " _YELLOW_("%04u-%02u-%02u %02u:%02u") " )%s", label, sprint_hex_inrow(v, 5), - 1900 + v[0], v[1], v[2], v[3], v[4]); + 1900 + v[0], v[1], v[2], v[3], v[4], suffix); } else { - PrintAndLogEx(INFO, "%s %s", label, sprint_hex_inrow(v, 5)); + PrintAndLogEx(INFO, "%s %s" "%s", label, sprint_hex_inrow(v, 5), suffix); } } -// What a VIGIK signature covers: the UID the card has to keep, the service it -// belongs to, and the window it is good for. static size_t vigik_expected_msg(const mfc_vigik_t *d, uint8_t *out) { size_t len = 0; @@ -277,16 +262,16 @@ int vigik_verify(mfc_vigik_t *d) { PrintAndLogEx(INFO, " Service code..... 0x%04X", (uint16_t)d->service_code); PrintAndLogEx(INFO, " Key version...... %u", d->key_version); PrintAndLogEx(INFO, " Services counter. %u", d->services_counter); - vigik_print_date(" Access date......", d->slot_access_date); + vigik_print_date(" Access date......", d->slot_access_date, ""); PrintAndLogEx(INFO, " DST duration..... %u", d->slot_dst_duration); - // The service code on the card says which service issued it, so it should - // name the key that just verified. Where it does not, the table pairs a - // description with the wrong modulus - say so rather than quietly - // reporting the wrong issuer. + // The service code on the card says which service issued it, so it should name the key if (vigik_rsa_pk[i].code != (uint16_t)d->service_code) { PrintAndLogEx(WARNING, "Card says service 0x%04X but the key that verified is listed as 0x%04X ( %s )", - (uint16_t)d->service_code, vigik_rsa_pk[i].code, vigik_rsa_pk[i].desc); + (uint16_t)d->service_code, + vigik_rsa_pk[i].code, + vigik_rsa_pk[i].desc + ); } PrintAndLogEx(SUCCESS, "Signature verification: " _GREEN_("successful")); @@ -377,17 +362,21 @@ int vigik_annotate(mfc_vigik_t *d) { PrintAndLogEx(INFO, "MAD................. %s", sprint_hex_inrow(d->mad, sizeof(d->mad))); PrintAndLogEx(INFO, "Counters............ %u", d->counters); PrintAndLogEx(INFO, "rtf................. %s", sprint_hex_inrow(d->rtf, sizeof(d->rtf))); - PrintAndLogEx(INFO, "Service code........ 0x%08x / %u - " _YELLOW_("%s"), d->service_code, d->service_code, vigik_get_service(d->service_code)); + PrintAndLogEx(INFO, "Service code........ 0x%08x / %u - " _YELLOW_("%s") " " VIGIK_SIGNED, d->service_code, d->service_code, vigik_get_service(d->service_code)); PrintAndLogEx(INFO, "Info flag........... %u -", d->info_flag); // , sprint_bin(d->info_flag, 1)); - PrintAndLogEx(INFO, "Key version......... %u", d->key_version); + PrintAndLogEx(INFO, "Key version......... %u " VIGIK_SIGNED, d->key_version); PrintAndLogEx(INFO, "PTR Counter......... %u", d->ptr_counter); PrintAndLogEx(INFO, "Counter num......... %u", d->counter_num); - vigik_print_date("Slot access date....", d->slot_access_date); - PrintAndLogEx(INFO, "Slot dst duration... %u", d->slot_dst_duration); + vigik_print_date("Slot access date....", d->slot_access_date, " " VIGIK_SIGNED); + PrintAndLogEx(INFO, "Slot dst duration... %u " VIGIK_SIGNED, d->slot_dst_duration); PrintAndLogEx(INFO, "Other Slots......... %s", sprint_hex_inrow(d->other_slots, sizeof(d->other_slots))); - PrintAndLogEx(INFO, "Services counter.... %u", d->services_counter); - vigik_print_date("Loading date........", d->loading_date); + PrintAndLogEx(INFO, "Services counter.... %u " VIGIK_SIGNED, d->services_counter); + vigik_print_date("Loading date........", d->loading_date, ""); PrintAndLogEx(INFO, "Reserved null....... %u", d->reserved_null); + PrintAndLogEx(INFO, ""); + PrintAndLogEx(INFO, "Unmarked fields are outside the signature, %d of the %d service record", + 35, 48); + PrintAndLogEx(INFO, "bytes, and every one of them is writable with a published key"); PrintAndLogEx(INFO, "----------------------------------------------------------------"); PrintAndLogEx(INFO, ""); vigik_verify(d);