Merge pull request #1299 from merlokk/add_cp_cmds

Add commands to read cipurse transport card
This commit is contained in:
Oleg Moiseenko 2021-06-15 14:40:48 +03:00 committed by GitHub
commit fa546a8eb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 1753 additions and 0 deletions

View file

@ -211,6 +211,8 @@ set (TARGET_SOURCES
${PM3_ROOT}/client/src/fido/fidocore.c
${PM3_ROOT}/client/src/iso7816/apduinfo.c
${PM3_ROOT}/client/src/iso7816/iso7816core.c
${PM3_ROOT}/client/src/cipurse/cipursecrypto.c
${PM3_ROOT}/client/src/cipurse/cipursecore.c
${PM3_ROOT}/client/src/loclass/cipher.c
${PM3_ROOT}/client/src/loclass/cipherutils.c
${PM3_ROOT}/client/src/loclass/elite_crack.c
@ -242,6 +244,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/client/src/cmdhfepa.c
${PM3_ROOT}/client/src/cmdhffelica.c
${PM3_ROOT}/client/src/cmdhffido.c
${PM3_ROOT}/client/src/cmdhfcipurse.c
${PM3_ROOT}/client/src/cmdhficlass.c
${PM3_ROOT}/client/src/cmdhfjooki.c
${PM3_ROOT}/client/src/cmdhflegic.c

View file

@ -473,6 +473,7 @@ SRCS = aiddesfire.c \
cmdhfemrtd.c \
cmdhffelica.c \
cmdhffido.c \
cmdhfcipurse.c \
cmdhficlass.c \
cmdhflegic.c \
cmdhfjooki.c \
@ -556,6 +557,8 @@ SRCS = aiddesfire.c \
fido/cose.c \
fido/cbortools.c \
fido/fidocore.c \
cipurse/cipursecore.c \
cipurse/cipursecrypto.c \
fileutils.c \
flash.c \
generator.c \

View file

@ -0,0 +1,348 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2021 Merlok
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// CIPURSE transport cards data and commands
//-----------------------------------------------------------------------------
#include "cipursecore.h"
#include "commonutil.h" // ARRAYLEN
#include "comms.h" // DropField
#include "util_posix.h" // msleep
#include <string.h> // memcpy memset
#include "cmdhf14a.h"
#include "emv/emvcore.h"
#include "emv/emvjson.h"
#include "ui.h"
#include "util.h"
// context for secure channel
CipurseContext cipurseContext;
static int CIPURSEExchangeEx(bool ActivateField, bool LeaveFieldON, sAPDU apdu, bool IncludeLe, uint16_t Le, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
uint8_t data[APDU_RES_LEN] = {0};
uint8_t securedata[APDU_RES_LEN] = {0};
sAPDU secapdu;
*ResultLen = 0;
if (sw) *sw = 0;
uint16_t isw = 0;
int res = 0;
if (ActivateField) {
DropField();
msleep(50);
}
// long messages is not allowed
if (apdu.Lc > 228)
return 20;
// COMPUTE APDU
int datalen = 0;
uint16_t xle = IncludeLe ? 0x100 : 0x00;
if (xle == 0x100 && Le != 0)
xle = Le;
CipurseCAPDUReqEncode(&cipurseContext, &apdu, &secapdu, securedata, IncludeLe, Le);
if (APDUEncodeS(&secapdu, false, xle, data, &datalen)) {
PrintAndLogEx(ERR, "APDU encoding error.");
return 201;
}
if (GetAPDULogging())
PrintAndLogEx(SUCCESS, ">>>> %s", sprint_hex(data, datalen));
res = ExchangeAPDU14a(data, datalen, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen);
if (res) {
return res;
}
if (GetAPDULogging())
PrintAndLogEx(SUCCESS, "<<<< %s", sprint_hex(Result, *ResultLen));
if (*ResultLen < 2) {
return 200;
}
size_t rlen = 0;
if (*ResultLen == 2) {
if (cipurseContext.RequestSecurity == CPSMACed || cipurseContext.RequestSecurity == CPSEncrypted)
CipurseCClearContext(&cipurseContext);
isw = Result[0] * 0x0100 + Result[1];
} else {
CipurseCAPDURespDecode(&cipurseContext, Result, *ResultLen, securedata, &rlen, &isw);
memcpy(Result, securedata, rlen);
}
if (ResultLen != NULL)
*ResultLen = rlen;
if (sw != NULL)
*sw = isw;
if (isw != 0x9000) {
if (GetAPDULogging()) {
if (*sw >> 8 == 0x61) {
PrintAndLogEx(ERR, "APDU chaining len:%02x -->", *sw & 0xff);
} else {
PrintAndLogEx(ERR, "APDU(%02x%02x) ERROR: [%4X] %s", apdu.CLA, apdu.INS, isw, GetAPDUCodeDescription(*sw >> 8, *sw & 0xff));
return 5;
}
}
}
return PM3_SUCCESS;
}
static int CIPURSEExchange(sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
return CIPURSEExchangeEx(false, true, apdu, true, 0, Result, MaxResultLen, ResultLen, sw);
}
int CIPURSESelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
uint8_t data[] = {0x41, 0x44, 0x20, 0x46, 0x31};
CipurseCClearContext(&cipurseContext);
return EMVSelect(CC_CONTACTLESS, ActivateField, LeaveFieldON, data, sizeof(data), Result, MaxResultLen, ResultLen, sw, NULL);
}
int CIPURSEChallenge(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0x84, 0x00, 0x00, 0x00, NULL}, true, 0x16, Result, MaxResultLen, ResultLen, sw);
}
int CIPURSEMutalAuthenticate(uint8_t keyIndex, uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0x82, 0x00, keyIndex, paramslen, params}, true, 0x10, Result, MaxResultLen, ResultLen, sw);
}
int CIPURSECreateFile(uint8_t *attr, uint16_t attrlen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0xe4, 0x00, 0x00, attrlen, attr}, false, 0, Result, MaxResultLen, ResultLen, sw);
}
int CIPURSEDeleteFile(uint16_t fileID, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
uint8_t fileIdBin[] = {fileID >> 8, fileID & 0xff};
return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0xe4, 0x00, 0x00, 02, fileIdBin}, false, 0, Result, MaxResultLen, ResultLen, sw);
}
int CIPURSESelectFile(uint16_t fileID, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
uint8_t fileIdBin[] = {fileID >> 8, fileID & 0xff};
return CIPURSEExchange((sAPDU) {0x00, 0xa4, 0x00, 0x00, 02, fileIdBin}, Result, MaxResultLen, ResultLen, sw);
}
int CIPURSESelectMFFile(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
return CIPURSEExchange((sAPDU) {0x00, 0xa4, 0x00, 0x00, 0, NULL}, Result, MaxResultLen, ResultLen, sw);
}
int CIPURSEReadFileAttributes(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
return CIPURSEExchange((sAPDU) {0x80, 0xce, 0x00, 0x00, 0, NULL}, Result, MaxResultLen, ResultLen, sw);
}
int CIPURSEReadBinary(uint16_t offset, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
return CIPURSEExchange((sAPDU) {0x00, 0xb0, (offset >> 8) & 0x7f, offset & 0xff, 0, NULL}, Result, MaxResultLen, ResultLen, sw);
}
int CIPURSEUpdateBinary(uint16_t offset, uint8_t *data, uint16_t datalen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0xd6, (offset >> 8) & 0x7f, offset & 0xff, datalen, data}, true, 0, Result, MaxResultLen, ResultLen, sw);
}
bool CIPURSEChannelAuthenticate(uint8_t keyIndex, uint8_t *key, bool verbose) {
uint8_t buf[APDU_RES_LEN] = {0};
size_t len = 0;
uint16_t sw = 0;
CipurseContext cpc = {0};
CipurseCSetKey(&cpc, keyIndex, key);
// get RP, rP
int res = CIPURSEChallenge(buf, sizeof(buf), &len, &sw);
if (res != 0 || len != 0x16) {
if (verbose)
PrintAndLogEx(ERR, "Cipurse get challenge " _RED_("error") ". Card returns 0x%04x.", sw);
return false;
}
CipurseCSetRandomFromPICC(&cpc, buf);
// make auth data
uint8_t authparams[16 + 16 + 6] = {0};
CipurseCAuthenticateHost(&cpc, authparams);
// authenticate
res = CIPURSEMutalAuthenticate(keyIndex, authparams, sizeof(authparams), buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000 || len != 16) {
if (sw == 0x6988) {
if (verbose)
PrintAndLogEx(ERR, "Cipurse authentication " _RED_("error") ". Wrong key.");
} else if (sw == 0x6A88) {
if (verbose)
PrintAndLogEx(ERR, "Cipurse authentication " _RED_("error") ". Wrong key number.");
} else {
if (verbose)
PrintAndLogEx(ERR, "Cipurse authentication " _RED_("error") ". Card returns 0x%04x.", sw);
}
CipurseCClearContext(&cipurseContext);
return false;
}
if (CipurseCCheckCT(&cpc, buf)) {
if (verbose)
PrintAndLogEx(INFO, "Authentication " _GREEN_("OK"));
CipurseCChannelSetSecurityLevels(&cpc, CPSMACed, CPSMACed);
memcpy(&cipurseContext, &cpc, sizeof(CipurseContext));
return true;
} else {
if (verbose)
PrintAndLogEx(ERR, "Authentication " _RED_("ERROR") " card returned wrong CT");
CipurseCClearContext(&cipurseContext);
return false;
}
}
void CIPURSECSetActChannelSecurityLevels(CipurseChannelSecurityLevel req, CipurseChannelSecurityLevel resp) {
CipurseCChannelSetSecurityLevels(&cipurseContext, req, resp);
}
void CIPURSEPrintInfoFile(uint8_t *data, size_t len) {
if (len < 2) {
PrintAndLogEx(ERR, "Info file length " _RED_("ERROR"));
return;
}
PrintAndLogEx(INFO, "------------ INFO ------------");
PrintAndLogEx(INFO, "CIPURSE version %d revision %d", data[0], data[1]);
}
static void CIPURSEPrintFileDescriptor(uint8_t desc) {
if (desc == 0x01)
PrintAndLogEx(INFO, "Binary file");
else if (desc == 0x11)
PrintAndLogEx(INFO, "Binary file with transactions");
else if (desc == 0x02)
PrintAndLogEx(INFO, "Linear record file");
else if (desc == 0x12)
PrintAndLogEx(INFO, "Linear record file with transactions");
else if (desc == 0x06)
PrintAndLogEx(INFO, "Cyclic record file");
else if (desc == 0x16)
PrintAndLogEx(INFO, "Cyclic record file with transactions");
else if (desc == 0x1E)
PrintAndLogEx(INFO, "Linear value-record file");
else if (desc == 0x1F)
PrintAndLogEx(INFO, "Linear value-record file with transactions");
else
PrintAndLogEx(INFO, "Unknown file 0x%02x", desc);
}
static void CIPURSEPrintKeyAttrib(uint8_t *attr) {
PrintAndLogEx(INFO, "-------- KEY ATTRIBUTES --------");
PrintAndLogEx(INFO, "Additional info: 0x%02x", attr[0]);
PrintAndLogEx(INFO, "Key length: %d", attr[1]);
PrintAndLogEx(INFO, "Algorithm ID: 0x%02x", attr[2]);
PrintAndLogEx(INFO, "Security attr: 0x%02x", attr[3]);
PrintAndLogEx(INFO, "KVV: 0x%02x%02x%02x", attr[4], attr[5], attr[6]);
PrintAndLogEx(INFO, "-------------------------------");
}
void CIPURSEPrintFileAttr(uint8_t *fileAttr, size_t len) {
if (len < 7) {
PrintAndLogEx(ERR, "Attributes length " _RED_("ERROR"));
return;
}
PrintAndLogEx(INFO, "--------- FILE ATTRIBUTES ---------");
if (fileAttr[0] == 0x38) {
PrintAndLogEx(INFO, "Type: MF, ADF");
if (fileAttr[1] == 0x00) {
PrintAndLogEx(INFO, "Type: MF");
} else {
if ((fileAttr[1] & 0xe0) == 0x00)
PrintAndLogEx(INFO, "Type: Unknown");
if ((fileAttr[1] & 0xe0) == 0x20)
PrintAndLogEx(INFO, "Type: CIPURSE L");
if ((fileAttr[1] & 0xe0) == 0x40)
PrintAndLogEx(INFO, "Type: CIPURSE S");
if ((fileAttr[1] & 0xe0) == 0x60)
PrintAndLogEx(INFO, "Type: CIPURSE T");
if ((fileAttr[1] & 0x02) == 0x00)
PrintAndLogEx(INFO, "Autoselect on PxSE select OFF");
else
PrintAndLogEx(INFO, "Autoselect on PxSE select ON");
if ((fileAttr[1] & 0x01) == 0x00)
PrintAndLogEx(INFO, "PxSE select returns FCPTemplate OFF");
else
PrintAndLogEx(INFO, "PxSE select returns FCPTemplate ON");
}
PrintAndLogEx(INFO, "File ID: 0x%02x%02x", fileAttr[2], fileAttr[3]);
PrintAndLogEx(INFO, "Maximum number of custom EFs: %d", fileAttr[4]);
PrintAndLogEx(INFO, "Maximum number of EFs with SFID: %d", fileAttr[5]);
uint8_t keyNum = fileAttr[6];
PrintAndLogEx(INFO, "Keys assigned: %d", keyNum);
if (len >= 9) {
PrintAndLogEx(INFO, "SMR entries: %02x%02x", fileAttr[7], fileAttr[8]);
}
if (len >= 10 + keyNum + 1) {
PrintAndLogEx(INFO, "ART: %s", sprint_hex(&fileAttr[9], keyNum + 1));
}
if (len >= 11 + keyNum + 1 + keyNum * 7) {
for (int i = 0; i < keyNum; i++) {
PrintAndLogEx(INFO, "Key %d Attributes: %s", i, sprint_hex(&fileAttr[11 + keyNum + 1 + i * 7], 7));
CIPURSEPrintKeyAttrib(&fileAttr[11 + keyNum + 1 + i * 7]);
}
}
// MF
if (fileAttr[1] == 0x00) {
PrintAndLogEx(INFO, "Total memory size: %d", (fileAttr[len - 6] << 16) + (fileAttr[len - 1] << 5) + fileAttr[len - 4]);
PrintAndLogEx(INFO, "Free memory size: %d", (fileAttr[len - 3] << 16) + (fileAttr[len - 2] << 8) + fileAttr[len - 1]);
} else {
int ptr = 11 + keyNum + 1 + keyNum * 7;
if (len > ptr)
PrintAndLogEx(INFO, "TLV file control: %s", sprint_hex(&fileAttr[ptr], len - ptr));
}
} else {
PrintAndLogEx(INFO, "Type: EF");
CIPURSEPrintFileDescriptor(fileAttr[0]);
if (fileAttr[1] == 0)
PrintAndLogEx(INFO, "SFI: not assigned");
else
PrintAndLogEx(INFO, "SFI: 0x%02x", fileAttr[1]);
PrintAndLogEx(INFO, "File ID: 0x%02x%02x", fileAttr[2], fileAttr[3]);
if (fileAttr[0] == 0x01 || fileAttr[0] == 0x11)
PrintAndLogEx(INFO, "File size: %d", (fileAttr[4] << 8) + fileAttr[5]);
else
PrintAndLogEx(INFO, "Record num: %d record size: %d", fileAttr[4], fileAttr[5]);
PrintAndLogEx(INFO, "Keys assigned: %d", fileAttr[6]);
if (len >= 9) {
PrintAndLogEx(INFO, "SMR entries: %02x%02x", fileAttr[7], fileAttr[8]);
}
if (len >= 10) {
PrintAndLogEx(INFO, "ART: %s", sprint_hex(&fileAttr[9], len - 9));
if (fileAttr[6] + 1 != len - 9)
PrintAndLogEx(WARNING, "ART length is wrong");
}
}
}

View file

@ -0,0 +1,44 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2021 Merlok
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// CIPURSE transport cards data and commands
//-----------------------------------------------------------------------------
#ifndef __CIPURSECORE_H__
#define __CIPURSECORE_H__
#include "common.h"
#include <jansson.h>
#include "iso7816/apduinfo.h" // sAPDU
#include "cipurse/cipursecrypto.h"
#define CIPURSE_DEFAULT_KEY {0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73}
void CIPURSEPrintInfoFile(uint8_t *data, size_t len);
int CIPURSESelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
int CIPURSEChallenge(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
int CIPURSEMutalAuthenticate(uint8_t keyIndex, uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
int CIPURSECreateFile(uint8_t *attr, uint16_t attrlen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
int CIPURSEDeleteFile(uint16_t fileID, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
int CIPURSESelectMFFile(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) ;
int CIPURSESelectFile(uint16_t fileID, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
int CIPURSEReadFileAttributes(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
int CIPURSEReadBinary(uint16_t offset, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
int CIPURSEUpdateBinary(uint16_t offset, uint8_t *data, uint16_t datalen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
bool CIPURSEChannelAuthenticate(uint8_t keyIndex, uint8_t *key, bool verbose);
void CIPURSECSetActChannelSecurityLevels(CipurseChannelSecurityLevel req, CipurseChannelSecurityLevel resp);
void CIPURSEPrintFileAttr(uint8_t *fileAttr, size_t len);
#endif /* __CIPURSECORE_H__ */

View file

@ -0,0 +1,528 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2021 Merlok
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// CIPURSE crypto primitives
//-----------------------------------------------------------------------------
#include "cipursecrypto.h"
#include "commonutil.h" // ARRAYLEN
#include "comms.h" // DropField
#include "util_posix.h" // msleep
#include <string.h> // memcpy memset
#include "cmdhf14a.h"
#include "emv/emvcore.h"
#include "emv/emvjson.h"
#include "crypto/libpcrypto.h"
#include "ui.h"
#include "util.h"
uint8_t AESData0[CIPURSE_AES_KEY_LENGTH] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t QConstant[CIPURSE_AES_KEY_LENGTH] = {0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74};
uint8_t CipurseCSecurityLevelEnc(CipurseChannelSecurityLevel lvl) {
switch (lvl) {
case CPSNone:
return 0x00;
case CPSPlain:
return 0x00;
case CPSMACed:
return 0x01;
case CPSEncrypted:
return 0x02;
default:
return 0x00;
}
}
static void bin_xor(uint8_t *d1, uint8_t *d2, size_t len) {
for (size_t i = 0; i < len; i++)
d1[i] = d1[i] ^ d2[i];
}
static void bin_ext(uint8_t *dst, size_t dstlen, uint8_t *src, size_t srclen) {
if (srclen > dstlen)
memcpy(dst, &src[srclen - dstlen], dstlen);
else
memcpy(dst, src, dstlen);
}
static void bin_pad(uint8_t *dst, size_t dstlen, uint8_t *src, size_t srclen) {
memset(dst, 0, dstlen);
if (srclen <= dstlen)
memcpy(&dst[dstlen - srclen], src, srclen);
else
memcpy(dst, src, dstlen);
}
static void bin_pad2(uint8_t *dst, size_t dstlen, uint8_t *src, size_t srclen) {
memset(dst, 0, dstlen);
uint8_t dbl[srclen * 2];
memcpy(dbl, src, srclen);
memcpy(&dbl[srclen], src, srclen);
bin_pad(dst, dstlen, dbl, srclen * 2);
}
static uint64_t rotateLeft48(uint64_t src) {
uint64_t dst = src << 1;
if (dst & 0x0001000000000000UL) {
dst = dst | 1;
dst = dst & 0x0000ffffffffffffUL;
}
return dst;
}
static uint64_t computeNLM48(uint64_t x, uint64_t y) {
uint64_t res = 0;
for (int i = 0; i < 48; i++) {
res = rotateLeft48(res);
if (res & 1)
res = res ^ CIPURSE_POLY;
y = rotateLeft48(y);
if (y & 1)
res = res ^ x;
}
return res;
}
static void computeNLM(uint8_t *res, uint8_t *x, uint8_t *y) {
uint64_t x64 = 0;
uint64_t y64 = 0;
for (int i = 0; i < 6; i++) {
x64 = (x64 << 8) | x[i];
y64 = (y64 << 8) | y[i];
}
uint64_t res64 = computeNLM48(x64, y64);
for (int i = 0; i < 6; i++) {
res[5 - i] = res64 & 0xff;
res64 = res64 >> 8;
}
}
static void CipurseCGenerateK0AndCp(CipurseContext *ctx) {
uint8_t temp1[CIPURSE_AES_KEY_LENGTH] = {0};
uint8_t temp2[CIPURSE_AES_KEY_LENGTH] = {0};
uint8_t kp[CIPURSE_SECURITY_PARAM_N] = {0};
// session key derivation function
// kP := NLM(EXT(kID), rP)
// k0 := AES(key=PAD2(kP) XOR PAD(rT),kID) XOR kID
bin_ext(temp1, CIPURSE_SECURITY_PARAM_N, ctx->key, CIPURSE_AES_KEY_LENGTH);
computeNLM(kp, ctx->rP, temp1); // param sizes == 6 bytes
bin_pad2(temp1, CIPURSE_AES_KEY_LENGTH, kp, CIPURSE_SECURITY_PARAM_N);
bin_pad(temp2, CIPURSE_AES_KEY_LENGTH, ctx->rT, CIPURSE_SECURITY_PARAM_N);
bin_xor(temp1, temp2, CIPURSE_AES_KEY_LENGTH);
// session key K0
aes_encode(NULL, temp1, ctx->key, ctx->k0, CIPURSE_AES_KEY_LENGTH);
bin_xor(ctx->k0, ctx->key, CIPURSE_AES_KEY_LENGTH);
// first frame key k1, function to calculate k1,
// k1 := AES(key = RP; k0 XOR RT) XOR (k0 XOR RT)
memcpy(temp1, ctx->k0, CIPURSE_AES_KEY_LENGTH);
bin_xor(temp1, ctx->RT, CIPURSE_AES_KEY_LENGTH);
aes_encode(NULL, ctx->RP, temp1, temp2, CIPURSE_AES_KEY_LENGTH);
bin_xor(temp1, temp2, CIPURSE_AES_KEY_LENGTH);
memcpy(ctx->frameKey, temp1, CIPURSE_AES_KEY_LENGTH);
// function to caluclate cP := AES(key=k0, RP).
// terminal response
aes_encode(NULL, ctx->k0, ctx->RP, ctx->cP, CIPURSE_AES_KEY_LENGTH);
}
static void CipurseCGenerateCT(uint8_t *k0, uint8_t *RT, uint8_t *CT) {
aes_encode(NULL, k0, RT, CT, CIPURSE_AES_KEY_LENGTH);
}
// from: https://github.com/duychuongvn/cipurse-card-core/blob/master/src/main/java/com/github/duychuongvn/cirpusecard/core/security/securemessaging/CipurseSecureMessage.java#L68
void CipurseCGetKVV(uint8_t *key, uint8_t *kvv) {
uint8_t res[16] = {0};
aes_encode(NULL, key, AESData0, res, CIPURSE_AES_KEY_LENGTH);
memcpy(kvv, res, CIPURSE_KVV_LENGTH);
}
void CipurseCClearContext(CipurseContext *ctx) {
if (ctx == NULL)
return;
memset(ctx, 0, sizeof(CipurseContext));
}
void CipurseCSetKey(CipurseContext *ctx, uint8_t keyId, uint8_t *key) {
if (ctx == NULL)
return;
CipurseCClearContext(ctx);
ctx->keyId = keyId;
memcpy(ctx->key, key, member_size(CipurseContext, key));
}
void CipurseCChannelSetSecurityLevels(CipurseContext *ctx, CipurseChannelSecurityLevel req, CipurseChannelSecurityLevel resp) {
ctx->RequestSecurity = req;
ctx->ResponseSecurity = resp;
}
bool isCipurseCChannelSecuritySet(CipurseContext *ctx) {
return ((ctx->RequestSecurity != CPSNone) && (ctx->ResponseSecurity != CPSNone));
}
void CipurseCSetRandomFromPICC(CipurseContext *ctx, uint8_t *random) {
if (ctx == NULL)
return;
memcpy(ctx->RP, random, member_size(CipurseContext, RP));
memcpy(ctx->rP, random + member_size(CipurseContext, RP), member_size(CipurseContext, rP));
}
void CipurseCSetRandomHost(CipurseContext *ctx) {
memset(ctx->RT, 0x10, member_size(CipurseContext, RT));
memset(ctx->rT, 0x20, member_size(CipurseContext, rT));
}
uint8_t CipurseCGetSMI(CipurseContext *ctx, bool LePresent) {
uint8_t res = LePresent ? 1 : 0;
res = res | (CipurseCSecurityLevelEnc(ctx->ResponseSecurity) << 2);
res = res | (CipurseCSecurityLevelEnc(ctx->RequestSecurity) << 6);
return res;
}
static void CipurseCFillAuthData(CipurseContext *ctx, uint8_t *authdata) {
memcpy(authdata, ctx->cP, member_size(CipurseContext, cP));
memcpy(&authdata[member_size(CipurseContext, cP)], ctx->RT, member_size(CipurseContext, RT));
memcpy(&authdata[member_size(CipurseContext, cP) + member_size(CipurseContext, RT)], ctx->rT, member_size(CipurseContext, rT));
}
void CipurseCAuthenticateHost(CipurseContext *ctx, uint8_t *authdata) {
if (ctx == NULL)
return;
CipurseCSetRandomHost(ctx);
CipurseCGenerateK0AndCp(ctx);
CipurseCGenerateCT(ctx->k0, ctx->RT, ctx->CT);
if (authdata != NULL)
CipurseCFillAuthData(ctx, authdata);
}
bool CipurseCCheckCT(CipurseContext *ctx, uint8_t *CT) {
return (memcmp(CT, ctx->CT, CIPURSE_AES_KEY_LENGTH) == 0);
}
void AddISO9797M2Padding(uint8_t *ddata, size_t *ddatalen, uint8_t *sdata, size_t sdatalen, size_t blocklen) {
*ddatalen = sdatalen + 1;
*ddatalen += blocklen - *ddatalen % blocklen;
memset(ddata, 0, *ddatalen);
memcpy(ddata, sdata, sdatalen);
ddata[sdatalen] = ISO9797_M2_PAD_BYTE;
}
size_t FindISO9797M2PaddingDataLen(uint8_t *data, size_t datalen) {
for (int i = datalen; i > 0; i--) {
if (data[i - 1] == 0x80)
return i - 1;
if (data[i - 1] != 0x00)
return 0;
}
return 0;
}
static uint16_t CipurseCComputeMICCRC(uint8_t *data, size_t len) {
uint16_t initCRC = 0x6363;
for (size_t i = 0; i < len; i++) {
uint8_t ch = data[i] ^ initCRC;
ch = ch ^ ((ch << 4) & 0xff);
initCRC = (initCRC >> 8) ^ (ch << 8) ^ (ch << 3) ^ (ch >> 4);
}
return initCRC;
}
void CipurseCGenerateMIC(uint8_t *data, size_t datalen, uint8_t *mic) {
size_t plen = 0;
uint8_t pdata[datalen + CIPURSE_MIC_LENGTH];
memset(pdata, 0, sizeof(pdata));
// 0x00 padding
memcpy(pdata, data, datalen);
plen = datalen;
if (datalen % CIPURSE_MIC_LENGTH)
plen += CIPURSE_MIC_LENGTH - datalen % CIPURSE_MIC_LENGTH;
// crc
uint16_t crc1 = CipurseCComputeMICCRC(pdata, plen);
for (size_t i = 0; i < datalen; i += 4) {
uint8_t tmp1 = pdata[i + 0];
uint8_t tmp2 = pdata[i + 1];
pdata[i + 0] = pdata[i + 2];
pdata[i + 1] = pdata[i + 3];
pdata[i + 2] = tmp1;
pdata[i + 3] = tmp2;
}
uint16_t crc2 = CipurseCComputeMICCRC(pdata, plen);
if (mic != NULL) {
mic[0] = crc2 >> 8;
mic[1] = crc2 & 0xff;
mic[2] = crc1 >> 8;
mic[3] = crc1 & 0xff;
}
}
bool CipurseCCheckMIC(uint8_t *data, size_t datalen, uint8_t *mic) {
uint8_t xmic[CIPURSE_MIC_LENGTH] = {0};
CipurseCGenerateMIC(data, datalen, xmic);
return (memcmp(xmic, mic, CIPURSE_MIC_LENGTH) == 0);
}
/* from: https://github.com/duychuongvn/cipurse-card-core/blob/master/src/main/java/com/github/duychuongvn/cirpusecard/core/security/crypto/CipurseCrypto.java#L521
*
* Encrypt/Decrypt the given data using ciphering mechanism explained the OPST.
* Data should be already padded.
*
* hx-1 := ki , hx := AES( key = hx-1 ; q) XOR q, Cx := AES( key = hx ;
* Dx ), hx+1 := AES( key = hx ; q ) XOR q, Cx+1 := AES( key = hx+1 ;
* Dx+1 ), ... hy := AES( key = hy-1 ; q ) XOR q, Cy := AES( key = hy ;
* Dy ), ki+1 := hy
*/
void CipurseCEncryptDecrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *dstdata, bool isEncrypt) {
uint8_t hx[CIPURSE_AES_KEY_LENGTH] = {0};
if (datalen == 0 || datalen % CIPURSE_AES_KEY_LENGTH != 0)
return;
memcpy(ctx->frameKeyNext, ctx->frameKey, CIPURSE_AES_KEY_LENGTH);
int i = 0;
while (datalen > i) {
aes_encode(NULL, QConstant, ctx->frameKeyNext, hx, CIPURSE_AES_KEY_LENGTH);
bin_xor(hx, ctx->frameKeyNext, CIPURSE_AES_KEY_LENGTH);
if (isEncrypt)
aes_encode(NULL, hx, &data[i], &dstdata[i], CIPURSE_AES_KEY_LENGTH);
else
aes_decode(NULL, hx, &data[i], &dstdata[i], CIPURSE_AES_KEY_LENGTH);
memcpy(ctx->frameKeyNext, hx, CIPURSE_AES_KEY_LENGTH);
i += CIPURSE_AES_KEY_LENGTH;
}
memcpy(ctx->frameKey, ctx->frameKeyNext, CIPURSE_AES_KEY_LENGTH);
}
void CipurseCChannelEncrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *encdata, size_t *encdatalen) {
uint8_t pdata[datalen + CIPURSE_AES_KEY_LENGTH];
size_t pdatalen = 0;
AddISO9797M2Padding(pdata, &pdatalen, data, datalen, CIPURSE_AES_KEY_LENGTH);
CipurseCEncryptDecrypt(ctx, pdata, pdatalen, encdata, true);
*encdatalen = pdatalen;
}
void CipurseCChannelDecrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *plaindata, size_t *plaindatalen) {
CipurseCEncryptDecrypt(ctx, data, datalen, plaindata, false);
*plaindatalen = FindISO9797M2PaddingDataLen(plaindata, datalen);
}
/* from: https://github.com/duychuongvn/cipurse-card-core/blob/master/src/main/java/com/github/duychuongvn/cirpusecard/core/security/crypto/CipurseCrypto.java#L473
*
* Generate OSPT MAC on the given input data.
* Data should be already padded.
*
* Calculation of Mi and ki+1: hx := ki , hx+1 := AES( key = hx ; Dx )
* XOR Dx , hx+2 := AES( key = hx+1 ; Dx+1 ) XOR Dx+1, hx+3 := AES( key =
* hx+2 ; Dx+2 ) XOR Dx+2, ... hy+1 := AES( key = hy ; Dy ) XOR Dy, ki+1 :=
* hy+1 M'i := AES( key = ki ; ki+1 ) XOR ki+1, Mi := m LS bits of M'i = (
* (M'i )0, (M'i )1, ..., (M'i )m-1)
*/
void CipurseCGenerateMAC(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac) {
uint8_t temp[CIPURSE_AES_KEY_LENGTH] = {0};
memcpy(ctx->frameKeyNext, ctx->frameKey, CIPURSE_AES_KEY_LENGTH);
int i = 0;
while (datalen > i) {
aes_encode(NULL, ctx->frameKeyNext, &data[i], temp, CIPURSE_AES_KEY_LENGTH);
bin_xor(temp, &data[i], CIPURSE_AES_KEY_LENGTH);
memcpy(ctx->frameKeyNext, temp, CIPURSE_AES_KEY_LENGTH);
i += CIPURSE_AES_KEY_LENGTH;
}
aes_encode(NULL, ctx->frameKey, ctx->frameKeyNext, temp, CIPURSE_AES_KEY_LENGTH);
bin_xor(temp, ctx->frameKeyNext, CIPURSE_AES_KEY_LENGTH);
memcpy(ctx->frameKey, ctx->frameKeyNext, CIPURSE_AES_KEY_LENGTH);
if (mac != NULL)
memcpy(mac, temp, CIPURSE_MAC_LENGTH);
}
void CipurseCCalcMACPadded(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac) {
uint8_t pdata[datalen + CIPURSE_AES_KEY_LENGTH];
size_t pdatalen = 0;
AddISO9797M2Padding(pdata, &pdatalen, data, datalen, CIPURSE_AES_KEY_LENGTH);
CipurseCGenerateMAC(ctx, pdata, pdatalen, mac);
}
bool CipurseCCheckMACPadded(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac) {
uint8_t xmac[CIPURSE_MAC_LENGTH] = {0};
CipurseCCalcMACPadded(ctx, data, datalen, xmac);
return (memcmp(mac, xmac, CIPURSE_MAC_LENGTH) == 0);
}
static void CipurseCAPDUMACEncode(CipurseContext *ctx, sAPDU *apdu, uint8_t originalLc, uint8_t *data, size_t *datalen) {
data[0] = apdu->CLA;
data[1] = apdu->INS;
data[2] = apdu->P1;
data[3] = apdu->P2;
data[4] = apdu->Lc;
*datalen = 5 + apdu->Lc;
if (ctx->RequestSecurity == CPSMACed || ctx->RequestSecurity == CPSEncrypted)
*datalen = 5 + originalLc;
memcpy(&data[5], apdu->data, *datalen);
}
void CipurseCAPDUReqEncode(CipurseContext *ctx, sAPDU *srcapdu, sAPDU *dstapdu, uint8_t *dstdatabuf, bool includeLe, uint8_t Le) {
uint8_t mac[CIPURSE_MAC_LENGTH] = {0};
uint8_t buf[260] = {0};
size_t buflen = 0;
memcpy(dstapdu, srcapdu, sizeof(sAPDU));
if (isCipurseCChannelSecuritySet(ctx) == false)
return;
dstapdu->CLA |= 0x04;
dstapdu->data = dstdatabuf;
dstapdu->data[0] = CipurseCGetSMI(ctx, includeLe);
dstapdu->Lc++;
memcpy(&dstdatabuf[1], srcapdu->data, srcapdu->Lc);
if (includeLe) {
dstapdu->data[dstapdu->Lc] = Le;
dstapdu->Lc++;
}
uint8_t originalLc = dstapdu->Lc;
switch (ctx->RequestSecurity) {
case CPSNone:
break;
case CPSPlain:
CipurseCAPDUMACEncode(ctx, dstapdu, originalLc, buf, &buflen);
CipurseCCalcMACPadded(ctx, buf, buflen, NULL);
break;
case CPSMACed:
dstapdu->Lc += CIPURSE_MAC_LENGTH;
CipurseCAPDUMACEncode(ctx, dstapdu, originalLc, buf, &buflen);
CipurseCCalcMACPadded(ctx, buf, buflen, mac);
memcpy(&dstdatabuf[dstapdu->Lc - CIPURSE_MAC_LENGTH], mac, CIPURSE_MAC_LENGTH);
break;
case CPSEncrypted:
dstapdu->Lc = srcapdu->Lc + CIPURSE_MIC_LENGTH;
dstapdu->Lc += CIPURSE_AES_BLOCK_LENGTH - dstapdu->Lc % CIPURSE_AES_BLOCK_LENGTH + 1; // 1 - SMI
if (includeLe)
dstapdu->Lc++;
CipurseCAPDUMACEncode(ctx, dstapdu, originalLc, buf, &buflen);
CipurseCGenerateMIC(buf, buflen, mac);
buf[0] = dstapdu->CLA;
buf[1] = dstapdu->INS;
buf[2] = dstapdu->P1;
buf[3] = dstapdu->P2;
memcpy(&buf[4], srcapdu->data, srcapdu->Lc);
memcpy(&buf[4 + srcapdu->Lc], mac, CIPURSE_MIC_LENGTH);
//PrintAndLogEx(INFO, "data plain[%d]: %s", 4 + srcapdu->Lc + CIPURSE_MIC_LENGTH, sprint_hex(buf, 4 + srcapdu->Lc + CIPURSE_MIC_LENGTH));
CipurseCChannelEncrypt(ctx, buf, 4 + srcapdu->Lc + CIPURSE_MIC_LENGTH, &dstdatabuf[1], &buflen);
break;
default:
break;
}
}
void CipurseCAPDURespDecode(CipurseContext *ctx, uint8_t *srcdata, size_t srcdatalen, uint8_t *dstdata, size_t *dstdatalen, uint16_t *sw) {
uint8_t buf[260] = {0};
size_t buflen = 0;
uint8_t micdata[260] = {0};
size_t micdatalen = 0;
if (dstdatalen != NULL)
*dstdatalen = 0;
if (sw != NULL)
*sw = 0;
if (srcdatalen < 2)
return;
srcdatalen -= 2;
uint16_t xsw = srcdata[srcdatalen] * 0x0100 + srcdata[srcdatalen + 1];
if (sw)
*sw = xsw;
if (isCipurseCChannelSecuritySet(ctx) == false) {
memcpy(dstdata, srcdata, srcdatalen);
if (dstdatalen != NULL)
*dstdatalen = srcdatalen;
return;
}
switch (ctx->ResponseSecurity) {
case CPSNone:
break;
case CPSPlain:
memcpy(buf, srcdata, srcdatalen);
buflen = srcdatalen;
memcpy(&buf[buflen], &srcdata[srcdatalen], 2);
buflen += 2;
CipurseCCalcMACPadded(ctx, buf, buflen, NULL);
memcpy(dstdata, srcdata, srcdatalen);
if (dstdatalen != NULL)
*dstdatalen = srcdatalen;
break;
case CPSMACed:
if (srcdatalen < CIPURSE_MAC_LENGTH)
return;
buflen = srcdatalen - CIPURSE_MAC_LENGTH;
memcpy(buf, srcdata, buflen);
memcpy(&buf[buflen], &srcdata[srcdatalen], 2);
buflen += 2;
srcdatalen -= CIPURSE_MAC_LENGTH;
if (CipurseCCheckMACPadded(ctx, buf, buflen, &srcdata[srcdatalen]) == false) {
PrintAndLogEx(WARNING, "APDU MAC is not valid!");
}
memcpy(dstdata, srcdata, srcdatalen);
if (dstdatalen != NULL)
*dstdatalen = srcdatalen;
break;
case CPSEncrypted:
CipurseCChannelDecrypt(ctx, srcdata, srcdatalen, buf, &buflen);
//PrintAndLogEx(INFO, "data plain[%d]: %s", buflen, sprint_hex(buf, buflen));
micdatalen = buflen - 2 - CIPURSE_MIC_LENGTH;
memcpy(micdata, buf, buflen);
memcpy(&micdata[micdatalen], &buf[buflen - 2], 2);
micdatalen += 2;
if (CipurseCCheckMIC(micdata, micdatalen, &buf[micdatalen - 2]) == false) {
PrintAndLogEx(ERR, "APDU response MIC is not valid!");
}
memcpy(dstdata, buf, micdatalen - 2);
if (dstdatalen != NULL)
*dstdatalen = micdatalen - 2;
if (sw)
*sw = micdata[micdatalen - 2] * 0x0100 + micdata[micdatalen - 1];
break;
default:
break;
}
}

View file

@ -0,0 +1,86 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2021 Merlok
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// CIPURSE crypto primitives
//-----------------------------------------------------------------------------
#ifndef __CIPURSECRYPTO_H__
#define __CIPURSECRYPTO_H__
#include "common.h"
#include "iso7816/apduinfo.h" // sAPDU
#define CIPURSE_KVV_LENGTH 4
#define CIPURSE_AES_KEY_LENGTH 16
#define CIPURSE_AES_BLOCK_LENGTH 16
#define CIPURSE_SECURITY_PARAM_N 6
#define CIPURSE_MAC_LENGTH 8
#define CIPURSE_MIC_LENGTH 4
#define CIPURSE_POLY 0x35b088cce172UL
#define ISO9797_M2_PAD_BYTE 0x80
#define member_size(type, member) sizeof(((type *)0)->member)
typedef enum {
CPSNone,
CPSPlain,
CPSMACed,
CPSEncrypted
} CipurseChannelSecurityLevel;
typedef struct CipurseContextS {
uint8_t keyId;
uint8_t key[CIPURSE_AES_KEY_LENGTH];
uint8_t RP[CIPURSE_AES_KEY_LENGTH];
uint8_t rP[CIPURSE_SECURITY_PARAM_N];
uint8_t RT[CIPURSE_AES_KEY_LENGTH];
uint8_t rT[CIPURSE_SECURITY_PARAM_N];
uint8_t k0[CIPURSE_AES_KEY_LENGTH];
uint8_t cP[CIPURSE_AES_KEY_LENGTH];
uint8_t CT[CIPURSE_AES_KEY_LENGTH];
uint8_t frameKey[CIPURSE_AES_KEY_LENGTH];
uint8_t frameKeyNext[CIPURSE_AES_KEY_LENGTH];
CipurseChannelSecurityLevel RequestSecurity;
CipurseChannelSecurityLevel ResponseSecurity;
} CipurseContext;
uint8_t CipurseCSecurityLevelEnc(CipurseChannelSecurityLevel lvl);
void CipurseCClearContext(CipurseContext *ctx);
void CipurseCSetKey(CipurseContext *ctx, uint8_t keyId, uint8_t *key);
void CipurseCSetRandomFromPICC(CipurseContext *ctx, uint8_t *random);
void CipurseCSetRandomHost(CipurseContext *ctx);
uint8_t CipurseCGetSMI(CipurseContext *ctx, bool LePresent);
void CipurseCAuthenticateHost(CipurseContext *ctx, uint8_t *authdata);
bool CipurseCCheckCT(CipurseContext *ctx, uint8_t *CT);
void CipurseCChannelSetSecurityLevels(CipurseContext *ctx, CipurseChannelSecurityLevel req, CipurseChannelSecurityLevel resp);
bool isCipurseCChannelSecuritySet(CipurseContext *ctx);
void AddISO9797M2Padding(uint8_t *ddata, size_t *ddatalen, uint8_t *sdata, size_t sdatalen, size_t blocklen);
size_t FindISO9797M2PaddingDataLen(uint8_t *data, size_t datalen);
void CipurseCGenerateMAC(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac);
void CipurseCCalcMACPadded(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac);
bool CipurseCCheckMACPadded(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac);
void CipurseCGenerateMIC(uint8_t *data, size_t datalen, uint8_t *mic);
bool CipurseCCheckMIC(uint8_t *data, size_t datalen, uint8_t *mic);
void CipurseCEncryptDecrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *dstdata, bool isEncrypt);
void CipurseCChannelEncrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *encdata, size_t *encdatalen);
void CipurseCChannelDecrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *plaindata, size_t *plaindatalen);
void CipurseCGetKVV(uint8_t *key, uint8_t *kvv);
void CipurseCAPDUReqEncode(CipurseContext *ctx, sAPDU *srcapdu, sAPDU *dstapdu, uint8_t *dstdatabuf, bool includeLe, uint8_t Le);
void CipurseCAPDURespDecode(CipurseContext *ctx, uint8_t *srcdata, size_t srcdatalen, uint8_t *dstdata, size_t *dstdatalen, uint16_t *sw);
#endif /* __CIPURSECRYPTO_H__ */

View file

@ -33,6 +33,7 @@
#include "cmdhftopaz.h" // TOPAZ
#include "cmdhffelica.h" // ISO18092 / FeliCa
#include "cmdhffido.h" // FIDO authenticators
#include "cmdhfcipurse.h" // CIPURSE transport cards
#include "cmdhfthinfilm.h" // Thinfilm
#include "cmdhflto.h" // LTO-CM
#include "cmdhfcryptorf.h" // CryptoRF
@ -128,6 +129,15 @@ int CmdHFSearch(const char *Cmd) {
}
}
PROMPT_CLEARLINE;
PrintAndLogEx(INPLACE, " Searching for Cipurse tag...");
if (IfPm3Iso14443a()) {
if (CheckCardCipurse()) {
PrintAndLogEx(SUCCESS, "\nValid " _GREEN_("Cipurse tag") " found\n");
res = PM3_SUCCESS;
}
}
// 14b is the longest test
PROMPT_CLEARLINE;
PrintAndLogEx(INPLACE, " Searching for ISO14443-B tag...");
@ -399,6 +409,7 @@ static command_t CommandTable[] = {
{"14b", CmdHF14B, AlwaysAvailable, "{ ISO14443B RFIDs... }"},
{"15", CmdHF15, AlwaysAvailable, "{ ISO15693 RFIDs... }"},
// {"cryptorf", CmdHFCryptoRF, AlwaysAvailable, "{ CryptoRF RFIDs... }"},
{"cipurse", CmdHFCipurse, AlwaysAvailable, "{ Cipurse transport Cards... }"},
{"epa", CmdHFEPA, AlwaysAvailable, "{ German Identification Card... }"},
{"emrtd", CmdHFeMRTD, AlwaysAvailable, "{ Machine Readable Travel Document... }"},
{"felica", CmdHFFelica, AlwaysAvailable, "{ ISO18092 / FeliCa RFIDs... }"},

705
client/src/cmdhfcipurse.c Normal file
View file

@ -0,0 +1,705 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2021 Merlok
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// High frequency FIDO U2F and FIDO2 contactless authenticators
//-----------------------------------------------------------------------------
//
// JAVA implementation here:
//
// https://github.com/duychuongvn/cipurse-card-core
//-----------------------------------------------------------------------------
#include "cmdhffido.h"
#include <unistd.h>
#include "cmdparser.h" // command_t
#include "commonutil.h"
#include "comms.h"
#include "proxmark3.h"
#include "emv/emvcore.h"
#include "emv/emvjson.h"
#include "cliparser.h"
#include "cmdhfcipurse.h"
#include "cipurse/cipursecore.h"
#include "cipurse/cipursecrypto.h"
#include "ui.h"
#include "cmdhf14a.h"
#include "cmdtrace.h"
#include "util.h"
#include "fileutils.h" // laodFileJSONroot
static int CmdHelp(const char *Cmd);
static int CmdHFCipurseInfo(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cipurse info",
"Get info from cipurse tags",
"hf cipurse info");
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
// info about 14a part
infoHF14A(false, false, false);
// CIPURSE info
PrintAndLogEx(INFO, "-----------" _CYAN_("CIPURSE Info") "---------------------------------");
SetAPDULogging(false);
uint8_t buf[APDU_RES_LEN] = {0};
size_t len = 0;
uint16_t sw = 0;
int res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw);
if (res) {
DropField();
return res;
}
if (sw != 0x9000) {
if (sw)
PrintAndLogEx(INFO, "Not a CIPURSE card! APDU response: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
else
PrintAndLogEx(ERR, "APDU exchange error. Card returns 0x0000.");
DropField();
return PM3_SUCCESS;
}
PrintAndLogEx(INFO, "Cipurse card: " _GREEN_("OK"));
res = CIPURSESelectFile(0x2ff7, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
DropField();
return PM3_SUCCESS;
}
res = CIPURSEReadBinary(0, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
DropField();
return PM3_SUCCESS;
}
if (len > 0) {
PrintAndLogEx(INFO, "Info file: " _GREEN_("OK"));
PrintAndLogEx(INFO, "[%d]: %s", len, sprint_hex(buf, len));
CIPURSEPrintInfoFile(buf, len);
}
DropField();
return PM3_SUCCESS;
}
static int CmdHFCipurseAuth(const char *Cmd) {
uint8_t buf[APDU_RES_LEN] = {0};
size_t len = 0;
uint16_t sw = 0;
uint8_t keyId = 1;
uint8_t key[] = {0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73};
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cipurse auth",
"Authenticate with key ID and key",
"hf cipurse auth -> Authenticate with keyID=1 and key = 7373...7373\n"
"hf cipurse auth -n 2 -k 65656565656565656565656565656565 -> Authenticate with key\n");
void *argtable[] = {
arg_param_begin,
arg_lit0("a", "apdu", "show APDU requests and responses"),
arg_lit0("v", "verbose", "show technical data"),
arg_int0("n", "keyid", "<dec>", "key id"),
arg_str0("k", "key", "<hex>", "key for authenticate"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool APDULogging = arg_get_lit(ctx, 1);
bool verbose = arg_get_lit(ctx, 2);
keyId = arg_get_int_def(ctx, 3, 1);
uint8_t hdata[250] = {0};
int hdatalen = sizeof(hdata);
CLIGetHexWithReturn(ctx, 4, hdata, &hdatalen);
if (hdatalen && hdatalen != 16) {
PrintAndLogEx(ERR, _RED_("ERROR:") " key length for AES128 must be 16 bytes only.");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (hdatalen)
memcpy(key, hdata, CIPURSE_AES_KEY_LENGTH);
SetAPDULogging(APDULogging);
CLIParserFree(ctx);
int res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
uint8_t kvv[CIPURSE_KVV_LENGTH] = {0};
CipurseCGetKVV(key, kvv);
if (verbose)
PrintAndLogEx(INFO, "Key id: %d key: %s KVV: %s", keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH), sprint_hex_inrow(kvv, CIPURSE_KVV_LENGTH));
bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose);
if (verbose == false) {
if (bres)
PrintAndLogEx(INFO, "Authentication " _GREEN_("OK"));
else
PrintAndLogEx(ERR, "Authentication " _RED_("ERROR"));
}
DropField();
return bres ? PM3_SUCCESS : PM3_ESOFT;
}
static int CLIParseKeyAndSecurityLevels(CLIParserContext *ctx, size_t keyid, size_t sreqid, size_t srespid, uint8_t *key, CipurseChannelSecurityLevel *sreq, CipurseChannelSecurityLevel *sresp) {
uint8_t hdata[250] = {0};
int hdatalen = sizeof(hdata);
CLIGetHexWithReturn(ctx, keyid, hdata, &hdatalen);
if (hdatalen && hdatalen != 16) {
PrintAndLogEx(ERR, _RED_("ERROR:") " key length for AES128 must be 16 bytes only.");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (hdatalen)
memcpy(key, hdata, CIPURSE_AES_KEY_LENGTH);
*sreq = CPSMACed;
*sresp = CPSMACed;
char cdata[250] = {0};
int cdatalen = sizeof(cdata);
cdatalen--; // for trailer 0x00
CLIGetStrWithReturn(ctx, sreqid, (uint8_t *)cdata, &cdatalen);
if (cdatalen) {
str_lower(cdata);
if (strcmp(cdata, "plain") == 0)
*sreq = CPSPlain;
else if (strcmp(cdata, "mac") == 0)
*sreq = CPSMACed;
else if (strcmp(cdata, "enc") == 0 || strcmp(cdata, "encode") == 0 || strcmp(cdata, "encrypted") == 0)
*sreq = CPSEncrypted;
else {
PrintAndLogEx(ERR, _RED_("ERROR:") " security level can be only: plain|mac|encode.");
return PM3_EINVARG;
}
}
cdatalen = sizeof(cdata);
memset(cdata, 0, cdatalen);
cdatalen--; // for trailer 0x00
CLIGetStrWithReturn(ctx, srespid, (uint8_t *)cdata, &cdatalen);
if (cdatalen) {
str_lower(cdata);
if (strcmp(cdata, "plain") == 0)
*sresp = CPSPlain;
else if (strcmp(cdata, "mac") == 0)
*sresp = CPSMACed;
else if (strcmp(cdata, "enc") == 0 || strcmp(cdata, "encode") == 0 || strcmp(cdata, "encrypted") == 0)
*sresp = CPSEncrypted;
else {
PrintAndLogEx(ERR, _RED_("ERROR:") " security level can be only: plain|mac|encode.");
return PM3_EINVARG;
}
}
return PM3_SUCCESS;
}
static int CmdHFCipurseReadFile(const char *Cmd) {
uint8_t buf[APDU_RES_LEN] = {0};
size_t len = 0;
uint16_t sw = 0;
uint8_t key[] = CIPURSE_DEFAULT_KEY;
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cipurse read",
"Read file by file ID with key ID and key",
"hf cipurse read -f 2ff7 -> Authenticate with keyID=1 and key = 7373...7373 and read file with id 2ff7\n"
"hf cipurse read -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> Authenticate with specified key and read file\n");
void *argtable[] = {
arg_param_begin,
arg_lit0("a", "apdu", "show APDU requests and responses"),
arg_lit0("v", "verbose", "show technical data"),
arg_int0("n", "keyid", "<dec>", "key id"),
arg_str0("k", "key", "<hex>", "key for authenticate"),
arg_str0("f", "file", "<hex>", "file ID"),
arg_int0("o", "offset", "<dec>", "offset for reading data from file"),
arg_lit0(NULL, "noauth", "read file without authentication"),
arg_str0(NULL, "sreq", "<plain|mac(default)|encode>", "communication reader-PICC security level"),
arg_str0(NULL, "sresp", "<plain|mac(default)|encode>", "communication PICC-reader security level"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool APDULogging = arg_get_lit(ctx, 1);
bool verbose = arg_get_lit(ctx, 2);
uint8_t keyId = arg_get_int_def(ctx, 3, 1);
CipurseChannelSecurityLevel sreq = CPSMACed;
CipurseChannelSecurityLevel sresp = CPSMACed;
int res = CLIParseKeyAndSecurityLevels(ctx, 4, 8, 9, key, &sreq, &sresp);
if (res) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
uint16_t fileId = 0x2ff7;
uint8_t hdata[250] = {0};
int hdatalen = sizeof(hdata);
CLIGetHexWithReturn(ctx, 5, hdata, &hdatalen);
if (hdatalen && hdatalen != 2) {
PrintAndLogEx(ERR, _RED_("ERROR:") " file id length must be 2 bytes only.");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (hdatalen)
fileId = (hdata[0] << 8) + hdata[1];
size_t offset = arg_get_int_def(ctx, 6, 0);
bool noAuth = arg_get_lit(ctx, 7);
SetAPDULogging(APDULogging);
CLIParserFree(ctx);
res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
if (verbose)
PrintAndLogEx(INFO, "File id: %x offset %d key id: %d key: %s", fileId, offset, keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH));
if (noAuth == false) {
bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose);
if (bres == false) {
if (verbose == false)
PrintAndLogEx(ERR, "Authentication " _RED_("ERROR"));
DropField();
return PM3_ESOFT;
}
// set channel security levels
CIPURSECSetActChannelSecurityLevels(sreq, sresp);
}
res = CIPURSESelectFile(fileId, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
if (verbose == false)
PrintAndLogEx(ERR, "File select " _RED_("ERROR") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
if (verbose)
PrintAndLogEx(INFO, "Select file 0x%x " _GREEN_("OK"), fileId);
res = CIPURSEReadBinary(offset, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
if (verbose == false)
PrintAndLogEx(ERR, "File read " _RED_("ERROR") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
if (len == 0)
PrintAndLogEx(INFO, "File id: %x is empty", fileId);
else
PrintAndLogEx(INFO, "File id: %x data[%d]: %s", fileId, len, sprint_hex(buf, len));
DropField();
return PM3_SUCCESS;
}
static int CmdHFCipurseWriteFile(const char *Cmd) {
uint8_t buf[APDU_RES_LEN] = {0};
size_t len = 0;
uint16_t sw = 0;
uint8_t key[] = CIPURSE_DEFAULT_KEY;
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cipurse write",
"Write file by file ID with key ID and key",
"hf cipurse write -f 2ff7 -> Authenticate with keyID=1 and key = 7373...7373 and write file with id 2ff7\n"
"hf cipurse write -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> Authenticate with specified key and write file\n");
void *argtable[] = {
arg_param_begin,
arg_lit0("a", "apdu", "show APDU requests and responses"),
arg_lit0("v", "verbose", "show technical data"),
arg_int0("n", "keyid", "<dec>", "key id"),
arg_str0("k", "key", "<hex>", "key for authenticate"),
arg_str0("f", "file", "<hex>", "file ID"),
arg_int0("o", "offset", "<dec>", "offset for reading data from file"),
arg_lit0(NULL, "noauth", "read file without authentication"),
arg_str0(NULL, "sreq", "<plain|mac(default)|encode>", "communication reader-PICC security level"),
arg_str0(NULL, "sresp", "<plain|mac(default)|encode>", "communication PICC-reader security level"),
arg_str0("c", "content", "<hex>", "new file content"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool APDULogging = arg_get_lit(ctx, 1);
bool verbose = arg_get_lit(ctx, 2);
uint8_t keyId = arg_get_int_def(ctx, 3, 1);
CipurseChannelSecurityLevel sreq = CPSMACed;
CipurseChannelSecurityLevel sresp = CPSMACed;
int res = CLIParseKeyAndSecurityLevels(ctx, 4, 8, 9, key, &sreq, &sresp);
if (res) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
uint16_t fileId = 0x2ff7;
uint8_t hdata[250] = {0};
int hdatalen = sizeof(hdata);
CLIGetHexWithReturn(ctx, 5, hdata, &hdatalen);
if (hdatalen && hdatalen != 2) {
PrintAndLogEx(ERR, _RED_("ERROR:") " file id length must be 2 bytes only.");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (hdatalen)
fileId = (hdata[0] << 8) + hdata[1];
size_t offset = arg_get_int_def(ctx, 6, 0);
bool noAuth = arg_get_lit(ctx, 7);
hdatalen = sizeof(hdata);
CLIGetHexWithReturn(ctx, 10, hdata, &hdatalen);
if (hdatalen == 0) {
PrintAndLogEx(ERR, _RED_("ERROR:") " file content length must be more 0.");
CLIParserFree(ctx);
return PM3_EINVARG;
}
SetAPDULogging(APDULogging);
CLIParserFree(ctx);
res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
if (verbose) {
PrintAndLogEx(INFO, "File id: %x offset %d key id: %d key: %s", fileId, offset, keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH));
PrintAndLogEx(INFO, "data[%d]: %s", hdatalen, sprint_hex(hdata, hdatalen));
}
if (noAuth == false) {
bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose);
if (bres == false) {
if (verbose == false)
PrintAndLogEx(ERR, "Authentication " _RED_("ERROR"));
DropField();
return PM3_ESOFT;
}
// set channel security levels
CIPURSECSetActChannelSecurityLevels(sreq, sresp);
}
res = CIPURSESelectFile(fileId, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
if (verbose == false)
PrintAndLogEx(ERR, "File select " _RED_("ERROR") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
if (verbose)
PrintAndLogEx(INFO, "Select file 0x%x " _GREEN_("OK"), fileId);
res = CIPURSEUpdateBinary(offset, hdata, hdatalen, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
if (verbose == false)
PrintAndLogEx(ERR, "File write " _RED_("ERROR") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
PrintAndLogEx(INFO, "File id: %x successfully written.", fileId);
DropField();
return PM3_SUCCESS;
}
static int CmdHFCipurseReadFileAttr(const char *Cmd) {
uint8_t buf[APDU_RES_LEN] = {0};
size_t len = 0;
uint16_t sw = 0;
uint8_t key[] = CIPURSE_DEFAULT_KEY;
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cipurse aread",
"Read file attributes by file ID with key ID and key",
"hf cipurse aread -f 2ff7 -> Authenticate with keyID=1 and key = 7373...7373 and read file attributes with id 2ff7\n"
"hf cipurse aread -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> Authenticate with specified key and read file attributes\n");
void *argtable[] = {
arg_param_begin,
arg_lit0("a", "apdu", "show APDU requests and responses"),
arg_lit0("v", "verbose", "show technical data"),
arg_int0("n", "keyid", "<dec>", "key id"),
arg_str0("k", "key", "<hex>", "key for authenticate"),
arg_str0("f", "file", "<hex>", "file ID"),
arg_lit0(NULL, "noauth", "read file attributes without authentication"),
arg_str0(NULL, "sreq", "<plain|mac(default)|encode>", "communication reader-PICC security level"),
arg_str0(NULL, "sresp", "<plain|mac(default)|encode>", "communication PICC-reader security level"),
arg_lit0(NULL, "sel-adf", "show info about ADF itself"),
arg_lit0(NULL, "sel-mf", "show info about master file"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool APDULogging = arg_get_lit(ctx, 1);
bool verbose = arg_get_lit(ctx, 2);
uint8_t keyId = arg_get_int_def(ctx, 3, 1);
CipurseChannelSecurityLevel sreq = CPSMACed;
CipurseChannelSecurityLevel sresp = CPSMACed;
int res = CLIParseKeyAndSecurityLevels(ctx, 4, 7, 8, key, &sreq, &sresp);
if (res) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
uint16_t fileId = 0x2ff7;
uint8_t hdata[250] = {0};
int hdatalen = sizeof(hdata);
CLIGetHexWithReturn(ctx, 5, hdata, &hdatalen);
if (hdatalen && hdatalen != 2) {
PrintAndLogEx(ERR, _RED_("ERROR:") " file id length must be 2 bytes only.");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (hdatalen)
fileId = (hdata[0] << 8) + hdata[1];
bool noAuth = arg_get_lit(ctx, 6);
bool seladf = arg_get_lit(ctx, 9);
bool selmf = arg_get_lit(ctx, 10);
SetAPDULogging(APDULogging);
CLIParserFree(ctx);
res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
if (verbose)
PrintAndLogEx(INFO, "File id: %x key id: %d key: %s", fileId, keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH));
if (noAuth == false) {
bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose);
if (bres == false) {
if (verbose == false)
PrintAndLogEx(ERR, "Authentication " _RED_("ERROR"));
DropField();
return PM3_ESOFT;
}
// set channel security levels
CIPURSECSetActChannelSecurityLevels(sreq, sresp);
}
if (seladf == false) {
if (selmf)
res = CIPURSESelectMFFile(buf, sizeof(buf), &len, &sw);
else
res = CIPURSESelectFile(fileId, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
if (verbose == false)
PrintAndLogEx(ERR, "File select " _RED_("ERROR") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
}
if (verbose)
PrintAndLogEx(INFO, "Select file 0x%x " _GREEN_("OK"), fileId);
res = CIPURSEReadFileAttributes(buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
if (verbose == false)
PrintAndLogEx(ERR, "File read " _RED_("ERROR") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
if (len == 0) {
PrintAndLogEx(WARNING, "File id: %x attributes is empty", fileId);
DropField();
return PM3_SUCCESS;
}
if (verbose)
PrintAndLogEx(INFO, "File id: %x attributes[%d]: %s", fileId, len, sprint_hex(buf, len));
CIPURSEPrintFileAttr(buf, len);
DropField();
return PM3_SUCCESS;
}
static int CmdHFCipurseDeleteFile(const char *Cmd) {
uint8_t buf[APDU_RES_LEN] = {0};
size_t len = 0;
uint16_t sw = 0;
uint8_t key[] = CIPURSE_DEFAULT_KEY;
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cipurse delete",
"Read file by file ID with key ID and key",
"hf cipurse delete -f 2ff7 -> Authenticate with keyID=1 and key = 7373...7373 and delete file with id 2ff7\n"
"hf cipurse delete -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> Authenticate with specified key and delete file\n");
void *argtable[] = {
arg_param_begin,
arg_lit0("a", "apdu", "show APDU requests and responses"),
arg_lit0("v", "verbose", "show technical data"),
arg_int0("n", "keyid", "<dec>", "key id"),
arg_str0("k", "key", "<hex>", "key for authenticate"),
arg_str0("f", "file", "<hex>", "file ID"),
arg_str0(NULL, "sreq", "<plain|mac(default)|encode>", "communication reader-PICC security level"),
arg_str0(NULL, "sresp", "<plain|mac(default)|encode>", "communication PICC-reader security level"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool APDULogging = arg_get_lit(ctx, 1);
bool verbose = arg_get_lit(ctx, 2);
uint8_t keyId = arg_get_int_def(ctx, 3, 1);
CipurseChannelSecurityLevel sreq = CPSMACed;
CipurseChannelSecurityLevel sresp = CPSMACed;
int res = CLIParseKeyAndSecurityLevels(ctx, 4, 6, 7, key, &sreq, &sresp);
if (res) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
uint16_t fileId = 0x2ff7;
uint8_t hdata[250] = {0};
int hdatalen = sizeof(hdata);
CLIGetHexWithReturn(ctx, 5, hdata, &hdatalen);
if (hdatalen && hdatalen != 2) {
PrintAndLogEx(ERR, _RED_("ERROR:") " file id length must be 2 bytes only.");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (hdatalen)
fileId = (hdata[0] << 8) + hdata[1];
SetAPDULogging(APDULogging);
CLIParserFree(ctx);
res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
if (verbose)
PrintAndLogEx(INFO, "File id: %x key id: %d key: %s", fileId, keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH));
bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose);
if (bres == false) {
if (verbose == false)
PrintAndLogEx(ERR, "Authentication " _RED_("ERROR"));
DropField();
return PM3_ESOFT;
}
// set channel security levels
CIPURSECSetActChannelSecurityLevels(sreq, sresp);
res = CIPURSEDeleteFile(fileId, buf, sizeof(buf), &len, &sw);
if (res != 0 || sw != 0x9000) {
if (verbose == false)
PrintAndLogEx(ERR, "File select " _RED_("ERROR") ". Card returns 0x%04x.", sw);
DropField();
return PM3_ESOFT;
}
PrintAndLogEx(INFO, "File id: 04x deleted " _GREEN_("succesfully"), fileId);
DropField();
return PM3_SUCCESS;
}
bool CheckCardCipurse(void) {
uint8_t buf[APDU_RES_LEN] = {0};
size_t len = 0;
uint16_t sw = 0;
int res = CIPURSESelect(true, false, buf, sizeof(buf), &len, &sw);
return (res == 0 && sw == 0x9000);
}
static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help."},
{"info", CmdHFCipurseInfo, IfPm3Iso14443a, "Info about Cipurse tag."},
{"auth", CmdHFCipurseAuth, IfPm3Iso14443a, "Authentication."},
{"read", CmdHFCipurseReadFile, IfPm3Iso14443a, "Read binary file."},
{"write", CmdHFCipurseWriteFile, IfPm3Iso14443a, "Write binary file."},
{"aread", CmdHFCipurseReadFileAttr, IfPm3Iso14443a, "Read file attributes."},
{"delete", CmdHFCipurseDeleteFile, IfPm3Iso14443a, "Delete file."},
{NULL, NULL, 0, NULL}
};
int CmdHFCipurse(const char *Cmd) {
clearCommandBuffer();
return CmdsParse(CommandTable, Cmd);
}
int CmdHelp(const char *Cmd) {
(void)Cmd; // Cmd is not used so far
CmdsHelp(CommandTable);
return PM3_SUCCESS;
}

25
client/src/cmdhfcipurse.h Normal file
View file

@ -0,0 +1,25 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2021 Merlok
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// High frequency FIDO U2F and FIDO2 contactless authenticators
//-----------------------------------------------------------------------------
//
// JAVA implementation here:
//
// https://github.com/duychuongvn/cipurse-card-core
//-----------------------------------------------------------------------------
#ifndef CMDHFCIPURSE_H__
#define CMDHFCIPURSE_H__
#include "common.h"
int CmdHFCipurse(const char *Cmd);
bool CheckCardCipurse(void);
#endif