From e9437a67391b1900fff780c45b89c1a2022ae366 Mon Sep 17 00:00:00 2001 From: Maxie Dion Schmidt Date: Sat, 12 Feb 2022 18:52:10 -0500 Subject: [PATCH] Preliminary (partial) support for more CommModes -- This is going to need substantial testing --- Doc/DESFireSupportReadme.md | 23 ++++++ .../Chameleon-Mini/Application/CryptoAES128.c | 20 +++++ .../Chameleon-Mini/Application/CryptoAES128.h | 10 ++- .../Chameleon-Mini/Application/CryptoCMAC.c | 13 +++ .../Chameleon-Mini/Application/CryptoCMAC.h | 4 +- .../DESFire/DESFireChameleonTerminal.c | 10 ++- .../Application/DESFire/DESFireUtils.c | 81 +++++++++++++++++++ .../Application/DESFire/DESFireUtils.h | 19 +++++ .../Application/MifareDESFire.c | 48 ++++++----- 9 files changed, 205 insertions(+), 23 deletions(-) diff --git a/Doc/DESFireSupportReadme.md b/Doc/DESFireSupportReadme.md index 76981c55..c771d56e 100644 --- a/Doc/DESFireSupportReadme.md +++ b/Doc/DESFireSupportReadme.md @@ -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: diff --git a/Firmware/Chameleon-Mini/Application/CryptoAES128.c b/Firmware/Chameleon-Mini/Application/CryptoAES128.c index 5d992e00..c931d838 100644 --- a/Firmware/Chameleon-Mini/Application/CryptoAES128.c +++ b/Firmware/Chameleon-Mini/Application/CryptoAES128.c @@ -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; +} diff --git a/Firmware/Chameleon-Mini/Application/CryptoAES128.h b/Firmware/Chameleon-Mini/Application/CryptoAES128.h index 75844ce7..20ccca8c 100644 --- a/Firmware/Chameleon-Mini/Application/CryptoAES128.h +++ b/Firmware/Chameleon-Mini/Application/CryptoAES128.h @@ -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) ({ \ @@ -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 diff --git a/Firmware/Chameleon-Mini/Application/CryptoCMAC.c b/Firmware/Chameleon-Mini/Application/CryptoCMAC.c index 63b0cda0..c78526df 100644 --- a/Firmware/Chameleon-Mini/Application/CryptoCMAC.c +++ b/Firmware/Chameleon-Mini/Application/CryptoCMAC.c @@ -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) { @@ -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; +} diff --git a/Firmware/Chameleon-Mini/Application/CryptoCMAC.h b/Firmware/Chameleon-Mini/Application/CryptoCMAC.h index d909ab40..aaf9e5a0 100644 --- a/Firmware/Chameleon-Mini/Application/CryptoCMAC.h +++ b/Firmware/Chameleon-Mini/Application/CryptoCMAC.h @@ -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 diff --git a/Firmware/Chameleon-Mini/Application/DESFire/DESFireChameleonTerminal.c b/Firmware/Chameleon-Mini/Application/DESFire/DESFireChameleonTerminal.c index 30dc9022..f8997814 100644 --- a/Firmware/Chameleon-Mini/Application/DESFire/DESFireChameleonTerminal.c +++ b/Firmware/Chameleon-Mini/Application/DESFire/DESFireChameleonTerminal.c @@ -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; diff --git a/Firmware/Chameleon-Mini/Application/DESFire/DESFireUtils.c b/Firmware/Chameleon-Mini/Application/DESFire/DESFireUtils.c index c29c3361..97edf1b3 100644 --- a/Firmware/Chameleon-Mini/Application/DESFire/DESFireUtils.c +++ b/Firmware/Chameleon-Mini/Application/DESFire/DESFireUtils.c @@ -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 */ diff --git a/Firmware/Chameleon-Mini/Application/DESFire/DESFireUtils.h b/Firmware/Chameleon-Mini/Application/DESFire/DESFireUtils.h index 9622772e..0b81298e 100644 --- a/Firmware/Chameleon-Mini/Application/DESFire/DESFireUtils.h +++ b/Firmware/Chameleon-Mini/Application/DESFire/DESFireUtils.h @@ -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 diff --git a/Firmware/Chameleon-Mini/Application/MifareDESFire.c b/Firmware/Chameleon-Mini/Application/MifareDESFire.c index 3f9f2712..1e5ffbc9 100644 --- a/Firmware/Chameleon-Mini/Application/MifareDESFire.c +++ b/Firmware/Chameleon-Mini/Application/MifareDESFire.c @@ -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; @@ -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): @@ -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;