mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2024-09-21 15:56:21 +08:00
Merge pull request #1450 from merlokk/desf_transact
Desfire transactions support
This commit is contained in:
commit
f1b81da975
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue