Merge pull request #1450 from merlokk/desf_transact

Desfire transactions support
This commit is contained in:
Oleg Moiseenko 2021-08-13 12:32:03 +03:00 committed by GitHub
commit f1b81da975
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 173 additions and 9 deletions

View file

@ -378,6 +378,7 @@ static uint8_t defaultKdfInput[50] = {0};
static DesfireSecureChannel defaultSecureChannel = DACEV1;
static DesfireCommandSet defaultCommSet = DCCNativeISO;
static DesfireCommunicationMode defaultCommMode = DCMPlain;
static uint32_t transactionCounter = 0;
static int CmdDesGetSessionParameters(CLIParserContext *ctx, DesfireContext *dctx,
uint8_t keynoid, uint8_t algoid, uint8_t keyid,
@ -3886,7 +3887,9 @@ static int CmdHF14ADesCreateTrMACFile(const char *Cmd) {
"Create Transaction MAC file in the application. Application master key needs to be provided or flag --no-auth set (depend on application settings).",
"--rawrights have priority over the separate rights settings.\n"
"Key/mode/etc of the authentication depends on application settings\n"
"hf mfdes createmacfile --aid 123456 --fid 01 --mackey --rawrights 1F30 00112233445566778899aabbccddeeff --mackeyver 01 -> create transaction mac file with parameters. Rights from default. Authentication with defaults from `default` command\n"
"Write right should be always 0xF. Read-write right should be 0xF if you not need to submit CommitReaderID command each time transaction starts\n"
"\n"
"hf mfdes createmacfile --aid 123456 --fid 01 --rawrights 0FF0 --mackey 00112233445566778899aabbccddeeff --mackeyver 01 -> create transaction mac file with parameters. Rights from default. Authentication with defaults from `default` command\n"
"hf mfdes createmacfile --aid 123456 --fid 01 --amode plain --rrights free --wrights deny --rwrights free --chrights key0 --mackey 00112233445566778899aabbccddeeff -> create file app=123456, file=01, with key, and mentioned rights with defaults from `default` command\n"
"hf mfdes createmacfile -n 0 -t des -k 0000000000000000 -f none --aid 123456 --fid 01 -> execute with default factory setup. key and keyver == 0x00..00");
@ -4540,6 +4543,7 @@ static int DesfileReadFileAndPrint(DesfireContext *dctx, uint8_t fnum, int filet
print_buffer_with_offset(resp, resplen, offset, true);
} else {
uint32_t cnt = MemLeToUint4byte(&resp[0]);
transactionCounter = cnt;
PrintAndLogEx(SUCCESS, "Transaction counter: %d (0x%08x)", cnt, cnt);
PrintAndLogEx(SUCCESS, "Transaction MAC : %s", sprint_hex(&resp[4], 8));
}
@ -4728,17 +4732,20 @@ static int CmdHF14ADesWriteData(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mfdes write",
"Write data from file. Key needs to be provided or flag --no-auth set (depend on file settings).",
"In the mode with CommitReaderID to decode previous reader id command needs to read transaction counter via dump/read command and specify --trkey\n"
"\n"
"hf mfdes write --aid 123456 --fid 01 -d 01020304 -> write file: app=123456, file=01, offset=0, get file type from card. use default channel settings from `default` command\n"
"hf mfdes write --aid 123456 --fid 01 --type data -d 01020304 --0ffset 000100 -> write data to std file with offset 0x100\n"
"hf mfdes write --aid 123456 --fid 01 --type data -d 01020304 --commit -> write data to backup file with commit\n"
"hf mfdes write --aid 123456 --fid 01 --type value -d 00000001 -> increment value file\n"
"hf mfdes write --aid 123456 --fid 01 --type value -d 00000001 --debit -> decrement value file\n"
"hf mfdes write --aid 123456 --fid 01 -d 01020304 -> write data to record file with `auto` type\n"
"hf mfdes write --aid 123456 --fid 01 -d 01020304 -> write data to file with `auto` type\n"
"hf mfdes write --aid 123456 --fid 01 --type record -d 01020304 -> write data to record file\n"
"hf mfdes write --aid 123456 --fid 01 --type record -d 01020304 --updaterec 0 -> update record in the record file. record 0 - lastest record.\n"
"hf mfdes write --aid 123456 --fid 01 --type record --offset 000000 -d 11223344 -> write record to record file. use default channel settings from `default` command\n"
"hf mfdes write --appisoid 1234 --fileisoid 1000 --type data -c iso -d 01020304 -> write data to std/backup file via iso commandset\n"
"hf mfdes write --appisoid 1234 --fileisoid 2000 --type record -c iso -d 01020304 -> aend record to record file via iso commandset");
"hf mfdes write --appisoid 1234 --fileisoid 2000 --type record -c iso -d 01020304 -> aend record to record file via iso commandset\n"
"hf mfdes write --aid 123456 --fid 01 -d 01020304 --readerid 010203 -> write data to file with CommitReaderID command before write and CommitTransaction after write");
void *argtable[] = {
arg_param_begin,
@ -4763,6 +4770,8 @@ static int CmdHF14ADesWriteData(const char *Cmd) {
arg_int0(NULL, "updaterec", "<record number dec>", "Record number for update record command. Updates record instead of write. Lastest record - 0"),
arg_str0(NULL, "appisoid", "<isoid hex>", "Application ISO ID (ISO DF ID) (2 hex bytes, big endian). Works only for ISO read commands."),
arg_str0(NULL, "fileisoid", "<isoid hex>", "File ISO ID (ISO DF ID) (2 hex bytes, big endian). Works only for ISO read commands."),
arg_str0(NULL, "readerid", "<hex>", "reader id for CommitReaderID command. If present - the command issued before write command."),
arg_str0(NULL, "trkey", "<hex>", "key for decode previous reader id."),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
@ -4826,6 +4835,24 @@ static int CmdHF14ADesWriteData(const char *Cmd) {
return PM3_EINVARG;
}
uint8_t readerid[250] = {0};
int readeridlen = sizeof(readerid);
CLIGetHexWithReturn(ctx, 22, readerid, &readeridlen);
if (readeridlen > 16) {
PrintAndLogEx(ERR, "ReaderID must be up to 16 bytes length.");
CLIParserFree(ctx);
return PM3_EINVARG;
}
uint8_t trkey[250] = {0};
int trkeylen = sizeof(trkey);
CLIGetHexWithReturn(ctx, 23, trkey, &trkeylen);
if (trkeylen > 0 && trkeylen != 16) {
PrintAndLogEx(ERR, "Transaction key must be 16 bytes length.");
CLIParserFree(ctx);
return PM3_EINVARG;
}
SetAPDULogging(APDULogging);
CLIParserFree(ctx);
@ -4834,6 +4861,10 @@ static int CmdHF14ADesWriteData(const char *Cmd) {
return PM3_EINVARG;
}
// get uid
if (trkeylen > 0)
DesfireGetCardUID(&dctx);
if (!isoidpresent) {
res = DesfireSelectAndAuthenticateEx(&dctx, securechann, appid, noauth, verbose);
if (res != PM3_SUCCESS) {
@ -4875,6 +4906,7 @@ static int CmdHF14ADesWriteData(const char *Cmd) {
case 0x00:
case 0x01: {
op = RFTData;
if (!commit)
commit = (fsettings.fileType == 0x01);
break;
}
@ -4916,6 +4948,39 @@ static int CmdHF14ADesWriteData(const char *Cmd) {
}
}
// CommitReaderID command
bool readeridpushed = false;
if (readeridlen > 0) {
uint8_t resp[250] = {0};
size_t resplen = 0;
DesfireCommunicationMode commMode = dctx.commMode;
DesfireSetCommMode(&dctx, DCMMACed);
res = DesfireCommitReaderID(&dctx, readerid, readeridlen, resp, &resplen);
DesfireSetCommMode(&dctx, commMode);
if (res == PM3_SUCCESS) {
PrintAndLogEx(INFO, _GREEN_("Commit Reader ID: "));
PrintAndLogEx(INFO, "Prev reader id encoded [%d]: %s", resplen, sprint_hex(resp, resplen));
if (trkeylen > 0) {
uint8_t sessionkey[16] = {0};
uint8_t uid[7] = {0};
memcpy(uid, dctx.uid, MAX(dctx.uidlen, 7));
DesfireGenTransSessionKey(trkey, transactionCounter, uid, false, sessionkey);
aes_decode(NULL, sessionkey, resp, resp, CRYPTO_AES_BLOCK_SIZE);
PrintAndLogEx(INFO, "Prev reader id [%d]: %s", resplen, sprint_hex(resp, resplen));
}
readeridpushed = true;
if (verbose)
PrintAndLogEx(INFO, "CommitReaderID " _GREEN_("OK"));
} else
PrintAndLogEx(WARNING, "Desfire CommitReaderID command " _RED_("error") ". Result: %d", res);
}
// write
if (op == RFTData) {
res = DesfireWriteFile(&dctx, fnum, offset, datalen, data);
if (res != PM3_SUCCESS) {
@ -4980,18 +5045,32 @@ static int CmdHF14ADesWriteData(const char *Cmd) {
}
// commit phase
if (commit) {
res = DesfireCommitTransaction(&dctx, false, 0);
if (commit || readeridpushed) {
uint8_t resp[250] = {0};
size_t resplen = 0;
DesfireSetCommMode(&dctx, DCMMACed);
res = DesfireCommitTransactionEx(&dctx, readeridpushed, 0x01, resp, &resplen);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Desfire CommitTransaction command " _RED_("error") ". Result: %d", res);
DropField();
return PM3_ESOFT;
}
if (verbose)
if (verbose) {
if (readeridpushed)
PrintAndLogEx(INFO, "TMC and TMV[%d]: %s", resplen, sprint_hex(resp, resplen));
PrintAndLogEx(INFO, "Commit " _GREEN_("OK"));
}
if (resplen == 4 + 8) {
PrintAndLogEx(INFO, _GREEN_("Commit result:"));
uint32_t cnt = MemLeToUint4byte(&resp[0]);
transactionCounter = cnt;
PrintAndLogEx(SUCCESS, "Transaction counter: %d (0x%08x)", cnt, cnt);
PrintAndLogEx(SUCCESS, "Transaction MAC : %s", sprint_hex(&resp[4], 8));
}
}
PrintAndLogEx(INFO, "Write %s file %02x " _GREEN_("success"), CLIGetOptionListStr(DesfireReadFileTypeOpts, op), fnum);
DropField();

View file

@ -1834,13 +1834,26 @@ int DesfireClearRecordFile(DesfireContext *dctx, uint8_t fnum) {
return DesfireCommandTxData(dctx, MFDES_CLEAR_RECORD_FILE, &fnum, 1);
}
int DesfireCommitTransaction(DesfireContext *dctx, bool enable_options, uint8_t options) {
int DesfireCommitReaderID(DesfireContext *dctx, uint8_t *readerid, size_t readeridlen, uint8_t *resp, size_t *resplen) {
uint8_t rid[16] = {0};
// command use 16b reader id only
memcpy(rid, readerid, MIN(readeridlen, 16));
return DesfireCommand(dctx, MFDES_COMMIT_READER_ID, rid, 16, resp, resplen, -1);
}
int DesfireCommitTransactionEx(DesfireContext *dctx, bool enable_options, uint8_t options, uint8_t *resp, size_t *resplen) {
if (enable_options)
return DesfireCommandTxData(dctx, MFDES_COMMIT_TRANSACTION, &options, 1);
return DesfireCommand(dctx, MFDES_COMMIT_TRANSACTION, &options, 1, resp, resplen, -1);
else
return DesfireCommandNoData(dctx, MFDES_COMMIT_TRANSACTION);
}
int DesfireCommitTransaction(DesfireContext *dctx, bool enable_options, uint8_t options) {
uint8_t resp[250] = {0};
size_t resplen = 0;
return DesfireCommitTransactionEx(dctx, enable_options, options, resp, &resplen);
}
int DesfireAbortTransaction(DesfireContext *dctx) {
return DesfireCommandNoData(dctx, MFDES_ABORT_TRANSACTION);
}
@ -2425,6 +2438,28 @@ void DesfirePrintCreateFileSettings(uint8_t filetype, uint8_t *data, size_t len)
DesfirePrintAccessRight(&data[xlen]);
xlen += 2;
// https://www.nxp.com/docs/en/data-sheet/MF2DLHX0.pdf
// page 14
// TransactionMAC file
if (filetype == 0x05) {
uint8_t read = 0;
uint8_t write = 0;
uint8_t readwrite = 0;
uint8_t change = 0;
DesfireDecodeFileAcessMode(&data[xlen - 2], &read, &write, &readwrite, &change);
if (write != 0x0f)
PrintAndLogEx(WARNING, "descr. : Write right should be set to F because write " _RED_("not allowed") ".");
if (readwrite == 0x0f)
PrintAndLogEx(SUCCESS, "descr. : ReadWrite right is %01X, CommitReaderID command disabled", readwrite);
else if (readwrite == 0x0e)
PrintAndLogEx(SUCCESS, "descr. : ReadWrite right is %01X, CommitReaderID command enabled with free access", readwrite);
else if (readwrite <= 0x04)
PrintAndLogEx(SUCCESS, "descr. : ReadWrite right is %01X, CommitReaderID command enabled with key 0x0%01x", readwrite, readwrite);
else
PrintAndLogEx(WARNING, "descr. : ReadWrite right must me 0..4,E,F instead of is %01X.", readwrite);
}
uint8_t reclen = 0;
DesfirePrintFileSettDynPart(filetype, &data[xlen], len - xlen, &reclen, true);
xlen += reclen;

View file

@ -229,6 +229,8 @@ void DesfirePrintCreateFileSettings(uint8_t filetype, uint8_t *data, size_t len)
const char *GetDesfireFileType(uint8_t type);
int DesfireCreateFile(DesfireContext *dctx, uint8_t ftype, uint8_t *fdata, size_t fdatalen, bool checklen);
int DesfireDeleteFile(DesfireContext *dctx, uint8_t fnum);
int DesfireCommitReaderID(DesfireContext *dctx, uint8_t *readerid, size_t readeridlen, uint8_t *resp, size_t *resplen);
int DesfireCommitTransactionEx(DesfireContext *dctx, bool enable_options, uint8_t options, uint8_t *resp, size_t *resplen);
int DesfireCommitTransaction(DesfireContext *dctx, bool enable_options, uint8_t options);
int DesfireAbortTransaction(DesfireContext *dctx);

View file

@ -619,6 +619,26 @@ int DesfireEV2CalcCMAC(DesfireContext *ctx, uint8_t cmd, uint8_t *data, size_t d
return aes_cmac8(NULL, ctx->sessionKeyMAC, mdata, mac, mdatalen);
}
// https://www.nxp.com/docs/en/data-sheet/MF2DLHX0.pdf
// page 42
void DesfireGenTransSessionKey(uint8_t *key, uint32_t trCntr, uint8_t *uid, bool forMAC, uint8_t *sessionkey) {
uint8_t xiv[CRYPTO_AES_BLOCK_SIZE] = {0};
if (forMAC) {
xiv[0] = 0x5a;
} else {
xiv[0] = 0xa5;
}
xiv[2] = 0x01;
xiv[4] = 0x80;
Uint4byteToMemLe(&xiv[5], trCntr + 1);
memcpy(&xiv[9], uid, 7);
DesfireContext ctx = {0};
DesfireSetKey(&ctx, 0, T_AES, key);
DesfireCryptoCMACEx(&ctx, DCOMainKey, xiv, 16, 0, sessionkey);
}
int desfire_get_key_length(DesfireCryptoAlgorythm key_type) {
switch (key_type) {
case T_DES:

View file

@ -137,6 +137,7 @@ void DesfireGenSessionKeyEV1(const uint8_t rnda[], const uint8_t rndb[], Desfire
void DesfireGenSessionKeyEV2(uint8_t *key, uint8_t *rndA, uint8_t *rndB, bool enckey, uint8_t *sessionkey);
void DesfireEV2FillIV(DesfireContext *ctx, bool ivforcommand, uint8_t *iv);
int DesfireEV2CalcCMAC(DesfireContext *ctx, uint8_t cmd, uint8_t *data, size_t datalen, uint8_t *mac);
void DesfireGenTransSessionKey(uint8_t *key, uint32_t trCntr, uint8_t *uid, bool forMAC, uint8_t *sessionkey);
int desfire_get_key_length(DesfireCryptoAlgorythm key_type);
size_t desfire_get_key_block_length(DesfireCryptoAlgorythm key_type);

View file

@ -73,6 +73,7 @@ static const AllowedChannelModesS AllowedChannelModes[] = {
{MFDES_FORMAT_PICC, DACd40, DCCNative, DCMMACed},
{MFDES_GET_FILE_IDS, DACd40, DCCNative, DCMMACed},
{MFDES_GET_ISOFILE_IDS, DACd40, DCCNative, DCMMACed},
{MFDES_COMMIT_READER_ID, DACd40, DCCNative, DCMMACed},
{MFDES_GET_UID, DACd40, DCCNative, DCMEncrypted},
{MFDES_CHANGE_KEY_SETTINGS, DACd40, DCCNative, DCMEncrypted},
@ -106,6 +107,7 @@ static const AllowedChannelModesS AllowedChannelModes[] = {
{MFDES_DEBIT, DACEV1, DCCNative, DCMMACed},
{MFDES_COMMIT_TRANSACTION, DACEV1, DCCNative, DCMMACed},
{MFDES_CLEAR_RECORD_FILE, DACEV1, DCCNative, DCMMACed},
{MFDES_COMMIT_READER_ID, DACEV1, DCCNative, DCMMACed},
{MFDES_GET_UID, DACEV1, DCCNative, DCMEncrypted},
{MFDES_CHANGE_KEY_SETTINGS, DACEV1, DCCNative, DCMEncrypted},

View file

@ -450,6 +450,30 @@ static bool TestEV2MAC(void) {
return res;
}
static bool TestTransSessionKeys(void) {
bool res = true;
uint8_t key[] = {0x66, 0xA8, 0xCB, 0x93, 0x26, 0x9D, 0xC9, 0xBC, 0x28, 0x85, 0xB7, 0xA9, 0x1B, 0x9C, 0x69, 0x7B};
uint8_t uid[] = {0x04, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
uint32_t trCntr = 8;
uint8_t sessionkey[16] = {0};
DesfireGenTransSessionKey(key, trCntr, uid, true, sessionkey);
uint8_t keymac[] = {0x7C, 0x1A, 0xD2, 0xD9, 0xC5, 0xC0, 0x81, 0x54, 0xA0, 0xA4, 0x91, 0x4B, 0x40, 0x1A, 0x65, 0x98};
res = res && (memcmp(sessionkey, keymac, sizeof(keymac)) == 0);
DesfireGenTransSessionKey(key, trCntr, uid, false, sessionkey);
uint8_t keyenc[] = {0x11, 0x9B, 0x90, 0x2A, 0x07, 0xB1, 0x8A, 0x86, 0x5B, 0x8E, 0x1B, 0x00, 0x60, 0x59, 0x47, 0x84};
res = res && (memcmp(sessionkey, keyenc, sizeof(keyenc)) == 0);
if (res)
PrintAndLogEx(INFO, "Trans session key. " _GREEN_("passed"));
else
PrintAndLogEx(ERR, "Trans session key. " _RED_("fail"));
return res;
}
bool DesfireTest(bool verbose) {
bool res = true;
@ -467,6 +491,7 @@ bool DesfireTest(bool verbose) {
res = res && TestEV2SessionKeys();
res = res && TestEV2IVEncode();
res = res && TestEV2MAC();
res = res && TestTransSessionKeys();
PrintAndLogEx(INFO, "---------------------------");
if (res)