Skip to content

Commit

Permalink
Preliminary (partial) support for more CommModes -- This is going to …
Browse files Browse the repository at this point in the history
…need substantial testing
  • Loading branch information
maxieds committed Feb 12, 2022
1 parent e9ce4ac commit e9437a6
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 23 deletions.
23 changes: 23 additions & 0 deletions Doc/DESFireSupportReadme.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,29 @@ DF_SETHDR=ATS 0675f7b102
```
Note that the UID for the tag can be set using separate Chameleon terminal commands.

#### DF_COMM_MODE -- Manually sets the communication mode of the current session

The supported (work in progress) DESFire communication modes include:
PLAINTEXT, PLAINTEXT-MAC, ENCIPHERED-CMAC-3DES, and ENCIPHERED-CMAC-AES128.
It should be clear from the prior commands issued in the session which ``CommMode``
congiguration we are supposed to be working within. This command let's the user
reset it intentionally at will for testing and debugging purposes.

The syntax is as follows:
```bash
DF_COMM_MODE?
DF_COMM_MODE=Plaintext
DF_COMM_MODE=Plaintext:MAC
DF_COMM_MODE=Enciphered:3K3DES
DF_COMM_MODE=Enciphered:AES128
```
Use of this experimental command may cause unexpected results, vulnerabilities exposing
your keys and sensitive (a priori) protected data to hackers and sniffers, and is
discouraged unless you know what you are doing :) Try not to report bugs with the
DESFire emulation if things suddenly fail after a call to this terminal command.
Putting the Chameleon through a full power recycle (battery off) should reset the setting
to the defaults.

#### DF_LOGMODE -- Sets the depth of (LIVE) logging messages printed at runtime

Syntax -- not guaranteeing that all of these are meaningful or distinct just yet:
Expand Down
20 changes: 20 additions & 0 deletions Firmware/Chameleon-Mini/Application/CryptoAES128.c
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,23 @@ void CryptoAESEncrypt_CBCReceive(uint16_t Count, uint8_t *PlainText, uint8_t *Ci
};
CryptoAES_CBCRecv(Count, PlainText, CipherText, IV, Key, CryptoSpec);
}

uint16_t appendBufferCRC32C(uint8_t *bufferData, uint16_t bufferSize) {
uint32_t workingCRC = INIT_CRC32C_VALUE;
for (int i = 0; i < bufferSize; i++) {
workingCRC = workingCRC ^ *(bufferData++);
for (int j = 0; j < 8; j++) {
if (workingCRC & 1) {
workingCRC = (workingCRC >> 1) ^ LE_CRC32C_POLYNOMIAL;
} else {
workingCRC = workingCRC >> 1;
}
}
}
// Append the CRC32C bytes in little endian byte order to the end of the buffer:
bufferData[bufferSize] = (uint8_t) (workingCRC & 0x000000FF);
bufferData[bufferSize + 1] = (uint8_t) ((workingCRC & 0x0000FF00) >> 8);
bufferData[bufferSize + 2] = (uint8_t) ((workingCRC & 0x00FF0000) >> 16);
bufferData[bufferSize + 4] = (uint8_t) ((workingCRC & 0xFF000000) >> 24);
return bufferSize + 4;
}
10 changes: 9 additions & 1 deletion Firmware/Chameleon-Mini/Application/CryptoAES128.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ void CryptoAESEncrypt_CBCSend(uint16_t Count, uint8_t *PlainText, uint8_t *Ciphe
uint8_t *Key, uint8_t *IV);

/* Crypto utility functions: */
#define CRYPTO_BYTES_TO_BLOCKS(numBytes, blockSize) \
#define CRYPTO_BYTES_TO_BLOCKS(numBytes, blockSize) \
( ((numBytes) + (blockSize) - 1) / (blockSize) )

#define CryptoMemoryXOR(inputBuf, destBuf, bufSize) ({ \
Expand All @@ -175,4 +175,12 @@ void CryptoAESEncrypt_CBCSend(uint16_t Count, uint8_t *PlainText, uint8_t *Ciphe
} \
})

/* The initial value is (-1) in one's complement: */
#define INIT_CRC32C_VALUE ((uint32_t) 0xFFFFFFFF)

/* The CCITT CRC-32 polynomial (modulo 2) in little endian (lsb-first) byte order: */
#define LE_CRC32C_POLYNOMIAL ((uint32_t) 0xEDB88320)

uint16_t appendBufferCRC32C(uint8_t *bufferData, uint16_t bufferSize);

#endif
13 changes: 13 additions & 0 deletions Firmware/Chameleon-Mini/Application/CryptoCMAC.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ static uint8_t _cmac_K2[CRYPTO_MAX_BLOCK_SIZE] = { 0x00 };
static uint8_t _cmac_RB[CRYPTO_MAX_BLOCK_SIZE] = { 0x00 };
static uint8_t _cmac_final[CRYPTO_MAX_BLOCK_SIZE] = { 0x00 };
static uint8_t _cmac_zeros[CRYPTO_MAX_BLOCK_SIZE] = { 0x00 };
static uint8_t _mac_key24[CRYPTO_MAX_KEY_SIZE] = { 0x00 };

static void getCMACSubK1(const uint8_t *bufferL, uint8_t blockSize, uint8_t polyByte, uint8_t *bufferOut);
static void getCMACSubK1(const uint8_t *bufferL, uint8_t blockSize, uint8_t polyByte, uint8_t *bufferOut) {
Expand Down Expand Up @@ -127,3 +128,15 @@ bool appendBufferCMAC(uint8_t cryptoType, const uint8_t *keyData, uint8_t *buffe
return appendBufferCMACSubroutine(cryptoType, keyData, _cmac_K1, _cmac_K2, IV, blockSize, bufferData, bufferSize);
}
}

uint16_t appendBufferMAC(const uint8_t *keyData, uint8_t *bufferData, uint16_t bufferSize) {
memcpy(&_mac_key24[2 * CRYPTO_DES_BLOCK_SIZE], keyData, CRYPTO_DES_BLOCK_SIZE);
memcpy(&_mac_key24[CRYPTO_DES_BLOCK_SIZE], keyData, CRYPTO_DES_BLOCK_SIZE);
memcpy(&_mac_key24[0], keyData, CRYPTO_DES_BLOCK_SIZE);
memset(&_cmac_zeros[0], 0x00, CRYPTO_DES_BLOCK_SIZE);
Encrypt3DESBuffer(bufferSize, bufferData, &bufferData[3 * CRYPTO_DES_BLOCK_SIZE], _cmac_zeros, keyData);
// Copy the 4-byte MAC from the ciphertext (end of the bufferData array):
memcpy(&_cmac_zeros[0], &bufferData[3 * CRYPTO_DES_BLOCK_SIZE + bufferSize - CRYPTO_DES_BLOCK_SIZE], 4);
memcpy(&bufferData[bufferSize], &_cmac_zeros[0], 4);
return bufferSize + 4;
}
4 changes: 3 additions & 1 deletion Firmware/Chameleon-Mini/Application/CryptoCMAC.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ This notice must be retained at the top of all source files where indicated.
#include "CryptoTDEA.h"
#include "CryptoAES128.h"

/* MAC and CMAC source code based on @github/andrade/nfcjlib */
/* CMAC and MAC source code based on @github/andrade/nfcjlib */

#define CRYPTO_CMAC_RB64 (0x1B)
#define CRYPTO_CMAC_RB128 ((uint8_t) 0x87)

bool appendBufferCMAC(uint8_t cryptoType, const uint8_t *keyData, uint8_t *bufferData, uint16_t bufferSize, uint8_t *IV);

uint16_t appendBufferMAC(const uint8_t *keyData, uint8_t *bufferData, uint16_t bufferSize);

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -225,16 +225,20 @@ CommandStatusIdType CommandDESFireSetCommMode(char *OutParam, const char *InPara
valueStr[15] = '\0';
if (!strcasecmp_P(valueStr, PSTR("Plaintext"))) {
DesfireCommMode = DESFIRE_COMMS_PLAINTEXT;
return COMMAND_INFO_OK;
DesfireCommandState.ActiveCommMode = DesfireCommMode;
return COMMAND_INFO_OK;
} else if (!strcasecmp_P(valueStr, PSTR("Plaintext:MAC"))) {
DesfireCommMode = DESFIRE_COMMS_PLAINTEXT_MAC;
DesfireCommandState.ActiveCommMode = DesfireCommMode;
return COMMAND_INFO_OK;
} else if (!strcasecmp_P(valueStr, PSTR("Enciphered:3K3DES"))) {
DesfireCommMode = DESFIRE_COMMS_CIPHERTEXT_DES;
return COMMAND_INFO_OK;
DesfireCommandState.ActiveCommMode = DesfireCommMode;
return COMMAND_INFO_OK;
} else if (!strcasecmp_P(valueStr, PSTR("Enciphered:AES128"))) {
DesfireCommMode = DESFIRE_COMMS_CIPHERTEXT_AES128;
return COMMAND_INFO_OK;
DesfireCommandState.ActiveCommMode = DesfireCommMode;
return COMMAND_INFO_OK;
}
snprintf_P(OutParam, TERMINAL_BUFFER_SIZE, PSTR("Options are: Plaintext|Plaintext:MAC|Enciphered:3K3DES|Enciphered:AES128"));
return COMMAND_ERR_INVALID_USAGE_ID;
Expand Down
81 changes: 81 additions & 0 deletions Firmware/Chameleon-Mini/Application/DESFire/DESFireUtils.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,85 @@ bool DesfireCheckParityBits(uint8_t *Buffer, uint16_t BitCount) {
return true;
}

uint16_t DesfirePreprocessAPDU(uint8_t CommMode, uint8_t *Buffer, uint16_t BufferSize) {
switch (CommMode) {
case DESFIRE_COMMS_PLAINTEXT:
// Remove the CRCA bytes at the end of the buffer:
return MAX(0, BufferSize - 2);
case DESFIRE_COMMS_PLAINTEXT_MAC: {
// TODO: We are not checking the MAC/CMAC bytes for consistency yet ...
uint16_t ChecksumBytes = 0;
if (DesfireCommandState.CryptoMethodType == CRYPTO_TYPE_DES || DesfireCommandState.CryptoMethodType == CRYPTO_TYPE_2KTDEA) {
ChecksumBytes = 4;
}
else if (DesfireCommandState.CryptoMethodType == CRYPTO_TYPE_3K3DES) {
ChecksumBytes = CRYPTO_3KTDEA_BLOCK_SIZE;
} else {
ChecksumBytes = CRYPTO_AES128_BLOCK_SIZE;
}
return MAX(0, BufferSize - ChecksumBytes);
}
case DESFIRE_COMMS_CIPHERTEXT_DES: {
// TODO ...
break;
}
case DESFIRE_COMMS_CIPHERTEXT_AES128: {
// TODO ...
break;
}
default:
break;
}
return BufferSize;
}

uint16_t DesfirePostprocessAPDU(uint8_t CommMode, uint8_t *Buffer, uint16_t BufferSize) {
switch (CommMode) {
case DESFIRE_COMMS_PLAINTEXT:
ISO14443AAppendCRCA(Buffer, BufferSize);
return BufferSize + 2;
case DESFIRE_COMMS_PLAINTEXT_MAC: {
if (DesfireCommandState.CryptoMethodType == CRYPTO_TYPE_DES || DesfireCommandState.CryptoMethodType == CRYPTO_TYPE_2KTDEA) {
return appendBufferMAC(SessionKey, Buffer, BufferSize);
} else {
// AES-128 or 3DES:
uint16_t MacedBytes = appendBufferCMAC(DesfireCommandState.CryptoMethodType, SessionKey, Buffer, BufferSize, SessionIV);
memcpy(SessionIV, &Buffer[BufferSize], MacedBytes - BufferSize);
return MacedBytes;
}
break;
}
case DESFIRE_COMMS_CIPHERTEXT_DES: {
// TripleDES:
uint16_t CryptoBlockSize = CRYPTO_3KTDEA_BLOCK_SIZE;
uint16_t BlockPadding = 0;
if ((BufferSize % CryptoBlockSize) != 0) {
BlockPadding = CryptoBlockSize - (BufferSize % CryptoBlockSize);
}
uint16_t MacedBytes = appendBufferCMAC(CRYPTO_TYPE_3K3DES, SessionKey, Buffer, BufferSize, SessionIV);
memset(&Buffer[MacedBytes], 0x00, BlockPadding);
uint16_t XferBytes = MacedBytes + BlockPadding;
Encrypt3DESBuffer(XferBytes, Buffer, &Buffer[XferBytes], SessionIV, SessionKey);
memmove(&Buffer[0], &Buffer[XferBytes], XferBytes);
return XferBytes;
}
case DESFIRE_COMMS_CIPHERTEXT_AES128: {
uint16_t CryptoBlockSize = CRYPTO_AES_BLOCK_SIZE;
uint16_t BlockPadding = 0;
if ((BufferSize % CryptoBlockSize) != 0) {
BlockPadding = CryptoBlockSize - (BufferSize % CryptoBlockSize);
}
uint16_t MacedBytes = appendBufferCMAC(CRYPTO_TYPE_AES128, SessionKey, Buffer, BufferSize, SessionIV);
memset(&Buffer[MacedBytes], 0x00, BlockPadding);
uint16_t XferBytes = MacedBytes + BlockPadding;
CryptoAESEncryptBuffer(XferBytes, Buffer, &Buffer[XferBytes], SessionIV, SessionKey);
memmove(&Buffer[0], &Buffer[XferBytes], XferBytes);
return XferBytes;
}
default:
break;
}
return BufferSize;
}

#endif /* CONFIG_MF_DESFIRE_SUPPORT */
19 changes: 19 additions & 0 deletions Firmware/Chameleon-Mini/Application/DESFire/DESFireUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,23 @@ uint16_t DesfireAddParityBits(uint8_t *Buffer, uint16_t bits);
uint16_t DesfireRemoveParityBits(uint8_t *Buffer, uint16_t BitCount);
bool DesfireCheckParityBits(uint8_t *Buffer, uint16_t BitCount);

/* Add utility wrapper functions to perform pre and postprocessing on
* the raw input APDU commands sent by the PCD depending on which
* CommMode (PLAINTEXT|PLAINTEXT-MAC|ENCIPHERED-CMAC-3DES|ECIPHERED-CMAC-AES128)
* setting is active.
*
* The implementation is adapted from the Java sources at
* @github/andrade/nfcjlib (in the DESFireEV1 source files).
* We will use the conventions in that library to update the SessionIV buffer
* when the next rounds of data are exchanged. Note that the SessionIV and
* SessionKey arrays are initialized in the Authenticate(Legacy|ISO|AES) commands
* used to initiate the working session from PCD <--> PICC.
*
* Helper methods to format and encode quirky or pathological cases of the
* CommSettings and wrapped APDU format combinations are defined statically in the
* C source file to save space in the symbol table for the firmware (ELF) binary.
*/
uint16_t DesfirePreprocessAPDU(uint8_t CommMode, uint8_t *Buffer, uint16_t BufferSize);
uint16_t DesfirePostprocessAPDU(uint8_t CommMode, uint8_t *Buffer, uint16_t BufferSize);

#endif
48 changes: 30 additions & 18 deletions Firmware/Chameleon-Mini/Application/MifareDESFire.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,22 +194,15 @@ uint16_t MifareDesfireProcess(uint8_t *Buffer, uint16_t BitCount) {
Buffer[0] = Buffer[1];
memmove(&Buffer[1], &Buffer[5], ByteCount);
/* Process the command */
/* TODO: Where are we deciphering wrapped payload data?
* This should depend on the CommMode standard?
*/
ByteCount = MifareDesfireProcessCommand(Buffer, ByteCount + 1);
/* TODO: Where are we re-wrapping the data according to the CommMode standards? */
if ((ByteCount != 0 && !Iso7816CLA(DesfireCmdCLA)) || (ByteCount == 1)) {
/* Re-wrap into padded APDU form */
Buffer[ByteCount] = Buffer[0];
memmove(&Buffer[0], &Buffer[1], ByteCount - 1);
Buffer[ByteCount - 1] = 0x91;
ISO14443AAppendCRCA(Buffer, ++ByteCount);
ByteCount += 2;
++ByteCount;
} else {
/* Re-wrap into ISO 7816-4 */
ISO14443AAppendCRCA(Buffer, ByteCount);
ByteCount += 2;
}
//LogEntry(LOG_INFO_DESFIRE_OUTGOING_DATA, Buffer, ByteCount);
return ByteCount * BITS_PER_BYTE;
Expand All @@ -223,12 +216,19 @@ uint16_t MifareDesfireProcess(uint8_t *Buffer, uint16_t BitCount) {
uint16_t MifareDesfireAppProcess(uint8_t *Buffer, uint16_t BitCount) {
uint16_t ByteCount = (BitCount + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
uint16_t ReturnedBytes = 0;
if (ByteCount >= 8 && DesfireCLA(Buffer[0]) && Buffer[2] == 0x00 &&
Buffer[3] == 0x00 && Buffer[4] == ByteCount - 8) {
return MifareDesfireProcess(Buffer, BitCount);
} else if (ByteCount >= 6 && DesfireCLA(Buffer[0]) && Buffer[2] == 0x00 &&
/* Is this first case really just a padded ISO7816 APDU with 2-byte prologue ??? */
//if (ByteCount >= 8 && DesfireCLA(Buffer[0]) && Buffer[2] == 0x00 &&
// Buffer[3] == 0x00 && Buffer[4] == ByteCount - 8) {
// return MifareDesfireProcess(Buffer, BitCount);
//}
if (ByteCount >= 6 && DesfireCLA(Buffer[0]) && Buffer[2] == 0x00 &&
Buffer[3] == 0x00 && Buffer[4] == ByteCount - 6) {
return MifareDesfireProcess(Buffer, BitCount);
uint16_t IncomingByteCount = (BitCount + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
uint16_t UnwrappedBitCount = DesfirePreprocessAPDU(DesfireCommMode, Buffer, IncomingByteCount) * BITS_PER_BYTE;
uint16_t ProcessedBitCount = MifareDesfireProcess(Buffer, UnwrappedBitCount);
uint16_t ProcessedByteCount = (ProcessedBitCount + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
ProcessedBitCount = DesfirePostprocessAPDU(DesfireCommMode, Buffer, ProcessedByteCount) * BITS_PER_BYTE;
return ProcessedBitCount;
} else if (ByteCount == 4 && Buffer[2] == 0x37 && Buffer[3] == 0xC8) {
// NXP-based PCD sent a "keep alive" response of ACK,
// so we respond with a corresponding NAK (with CRCA bytes appended):
Expand All @@ -240,19 +240,31 @@ uint16_t MifareDesfireAppProcess(uint8_t *Buffer, uint16_t BitCount) {
uint8_t ISO7816PrologueBytes[2];
memcpy(&ISO7816PrologueBytes[0], Buffer, 2);
memmove(&Buffer[0], &Buffer[2], ByteCount - 2);
uint16_t ProcessedBitCount = MifareDesfireProcess(Buffer, BitCount);
uint16_t IncomingByteCount = (BitCount + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
uint16_t UnwrappedBitCount = DesfirePreprocessAPDU(DesfireCommMode, Buffer, IncomingByteCount) * BITS_PER_BYTE;
uint16_t ProcessedBitCount = MifareDesfireProcess(Buffer, UnwrappedBitCount);
uint16_t ProcessedByteCount = (ProcessedBitCount + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
/* Append the same ISO7816 prologue bytes to the response: */
memmove(&Buffer[2], &Buffer[0], ProcessedByteCount);
memcpy(&Buffer[0], &ISO7816PrologueBytes[0], 2);
ISO14443AAppendCRCA(Buffer, ProcessedByteCount);
ProcessedBitCount += 2 * BITS_PER_BYTE;
ProcessedBitCount = DesfirePostprocessAPDU(DesfireCommMode, Buffer, ProcessedByteCount) * BITS_PER_BYTE;
return ProcessedBitCount;
} else if ((ReturnedBytes = CallInstructionHandler(Buffer, ByteCount)) != ISO14443A_APP_NO_RESPONSE) {
return ReturnedBytes;
/* This case should handle non-wrappped native commands. No pre/postprocessing afterwards: */
return ReturnedBytes;
} else if (!AnticolNoResp) {
uint16_t PiccProcessRespBytes = ISO144433APiccProcess(Buffer, BitCount);
/* This case is to exchange anticollision loop and RATS data. No need to pre/postprocess it depending
* on the CommMode, which has not been set yet if we reach this point:
*/
uint16_t PiccProcessRespBytes = ISO144433APiccProcess(Buffer, BitCount);
if (PiccProcessRespBytes == ISO14443A_APP_NO_RESPONSE) {
// Stop pesky USB readers trying to autodetect all tag types by brute-force enumeration
// from interfering with making it into the command exchange (DESFIRE_IDLE) states.
// Once the anticollision and/or RATS has completed, set this flag to keep it from
// resending that initial handshaking until the AppReset() function is called on a timeout.
// N.b., the ACR-122 reader does this repeatedly when trying to run the LibNFC testing code
// even when the reader has not been put in scan mode --
// and it really screws things up timing-wise!
AnticolNoResp = true;
}
return PiccProcessRespBytes;
Expand Down

0 comments on commit e9437a6

Please sign in to comment.