1 //-----------------------------------------------------------------------------
2 // Copyright (C) 2021 Merlok
4 // This code is licensed to you under the terms of the GNU GPL, version 2 or,
5 // at your option, any later version. See the LICENSE.txt file for the text of
7 //-----------------------------------------------------------------------------
8 // CIPURSE transport cards data and commands
9 //-----------------------------------------------------------------------------
11 #include "cipursecore.h"
13 #include "commonutil.h" // ARRAYLEN
14 #include "comms.h" // DropField
15 #include "util_posix.h" // msleep
16 #include <string.h> // memcpy memset
19 #include "emv/emvcore.h"
20 #include "emv/emvjson.h"
24 // context for secure channel
25 CipurseContext cipurseContext
;
27 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
) {
28 uint8_t data
[APDU_RES_LEN
] = {0};
29 uint8_t securedata
[APDU_RES_LEN
] = {0};
42 // long messages is not allowed
48 uint16_t xle
= IncludeLe
? 0x100 : 0x00;
49 if (xle
== 0x100 && Le
!= 0)
52 CipurseCAPDUReqEncode(&cipurseContext
, &apdu
, &secapdu
, securedata
, IncludeLe
, Le
);
54 if (APDUEncodeS(&secapdu
, false, xle
, data
, &datalen
)) {
55 PrintAndLogEx(ERR
, "APDU encoding error.");
60 PrintAndLogEx(SUCCESS
, ">>>> %s", sprint_hex(data
, datalen
));
62 res
= ExchangeAPDU14a(data
, datalen
, ActivateField
, LeaveFieldON
, Result
, (int)MaxResultLen
, (int *)ResultLen
);
68 PrintAndLogEx(SUCCESS
, "<<<< %s", sprint_hex(Result
, *ResultLen
));
75 if (*ResultLen
== 2) {
76 if (cipurseContext
.RequestSecurity
== CPSMACed
|| cipurseContext
.RequestSecurity
== CPSEncrypted
)
77 CipurseCClearContext(&cipurseContext
);
79 isw
= Result
[0] * 0x0100 + Result
[1];
81 CipurseCAPDURespDecode(&cipurseContext
, Result
, *ResultLen
, securedata
, &rlen
, &isw
);
82 memcpy(Result
, securedata
, rlen
);
85 if (ResultLen
!= NULL
)
92 if (GetAPDULogging()) {
93 if (*sw
>> 8 == 0x61) {
94 PrintAndLogEx(ERR
, "APDU chaining len:%02x -->", *sw
& 0xff);
96 PrintAndLogEx(ERR
, "APDU(%02x%02x) ERROR: [%4X] %s", apdu
.CLA
, apdu
.INS
, isw
, GetAPDUCodeDescription(*sw
>> 8, *sw
& 0xff));
105 static int CIPURSEExchange(sAPDU apdu
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
106 return CIPURSEExchangeEx(false, true, apdu
, true, 0, Result
, MaxResultLen
, ResultLen
, sw
);
109 int CIPURSESelect(bool ActivateField
, bool LeaveFieldON
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
110 uint8_t data
[] = {0x41, 0x44, 0x20, 0x46, 0x31};
111 CipurseCClearContext(&cipurseContext
);
113 return EMVSelect(CC_CONTACTLESS
, ActivateField
, LeaveFieldON
, data
, sizeof(data
), Result
, MaxResultLen
, ResultLen
, sw
, NULL
);
116 int CIPURSEChallenge(uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
117 return CIPURSEExchangeEx(false, true, (sAPDU
) {0x00, 0x84, 0x00, 0x00, 0x00, NULL
}, true, 0x16, Result
, MaxResultLen
, ResultLen
, sw
);
120 int CIPURSEMutalAuthenticate(uint8_t keyIndex
, uint8_t *params
, uint8_t paramslen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
121 return CIPURSEExchangeEx(false, true, (sAPDU
) {0x00, 0x82, 0x00, keyIndex
, paramslen
, params
}, true, 0x10, Result
, MaxResultLen
, ResultLen
, sw
);
124 int CIPURSECreateFile(uint8_t *attr
, uint16_t attrlen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
125 return CIPURSEExchangeEx(false, true, (sAPDU
) {0x00, 0xe4, 0x00, 0x00, attrlen
, attr
}, false, 0, Result
, MaxResultLen
, ResultLen
, sw
);
128 int CIPURSEDeleteFile(uint16_t fileID
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
129 uint8_t fileIdBin
[] = {fileID
>> 8, fileID
& 0xff};
130 return CIPURSEExchangeEx(false, true, (sAPDU
) {0x00, 0xe4, 0x00, 0x00, 02, fileIdBin
}, false, 0, Result
, MaxResultLen
, ResultLen
, sw
);
133 int CIPURSESelectFile(uint16_t fileID
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
134 uint8_t fileIdBin
[] = {fileID
>> 8, fileID
& 0xff};
135 return CIPURSEExchange((sAPDU
) {0x00, 0xa4, 0x00, 0x00, 02, fileIdBin
}, Result
, MaxResultLen
, ResultLen
, sw
);
138 int CIPURSESelectMFFile(uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
139 return CIPURSEExchange((sAPDU
) {0x00, 0xa4, 0x00, 0x00, 0, NULL
}, Result
, MaxResultLen
, ResultLen
, sw
);
142 int CIPURSEReadFileAttributes(uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
143 return CIPURSEExchange((sAPDU
) {0x80, 0xce, 0x00, 0x00, 0, NULL
}, Result
, MaxResultLen
, ResultLen
, sw
);
146 int CIPURSEReadBinary(uint16_t offset
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
147 return CIPURSEExchange((sAPDU
) {0x00, 0xb0, (offset
>> 8) & 0x7f, offset
& 0xff, 0, NULL
}, Result
, MaxResultLen
, ResultLen
, sw
);
150 int CIPURSEUpdateBinary(uint16_t offset
, uint8_t *data
, uint16_t datalen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
151 return CIPURSEExchange((sAPDU
) {0x00, 0xd6, (offset
>> 8) & 0x7f, offset
& 0xff, datalen
, data
}, Result
, MaxResultLen
, ResultLen
, sw
);
154 bool CIPURSEChannelAuthenticate(uint8_t keyIndex
, uint8_t *key
, bool verbose
) {
155 uint8_t buf
[APDU_RES_LEN
] = {0};
159 CipurseContext cpc
= {0};
160 CipurseCSetKey(&cpc
, keyIndex
, key
);
163 int res
= CIPURSEChallenge(buf
, sizeof(buf
), &len
, &sw
);
164 if (res
!= 0 || len
!= 0x16) {
166 PrintAndLogEx(ERR
, "Cipurse get challenge " _RED_("error") ". Card returns 0x%04x.", sw
);
170 CipurseCSetRandomFromPICC(&cpc
, buf
);
173 uint8_t authparams
[16 + 16 + 6] = {0};
174 CipurseCAuthenticateHost(&cpc
, authparams
);
177 res
= CIPURSEMutalAuthenticate(keyIndex
, authparams
, sizeof(authparams
), buf
, sizeof(buf
), &len
, &sw
);
178 if (res
!= 0 || sw
!= 0x9000 || len
!= 16) {
181 PrintAndLogEx(ERR
, "Cipurse authentication " _RED_("error") ". Wrong key.");
182 } else if (sw
== 0x6A88) {
184 PrintAndLogEx(ERR
, "Cipurse authentication " _RED_("error") ". Wrong key number.");
187 PrintAndLogEx(ERR
, "Cipurse authentication " _RED_("error") ". Card returns 0x%04x.", sw
);
190 CipurseCClearContext(&cipurseContext
);
194 if (CipurseCCheckCT(&cpc
, buf
)) {
196 PrintAndLogEx(INFO
, "Authentication " _GREEN_("OK"));
198 CipurseCChannelSetSecurityLevels(&cpc
, CPSMACed
, CPSMACed
);
199 memcpy(&cipurseContext
, &cpc
, sizeof(CipurseContext
));
203 PrintAndLogEx(ERR
, "Authentication " _RED_("ERROR") " card returned wrong CT");
205 CipurseCClearContext(&cipurseContext
);
210 void CIPURSECSetActChannelSecurityLevels(CipurseChannelSecurityLevel req
, CipurseChannelSecurityLevel resp
) {
211 CipurseCChannelSetSecurityLevels(&cipurseContext
, req
, resp
);
214 static void CIPURSEPrintPersoMode(uint8_t data
) {
216 PrintAndLogEx(INFO
, "Perso: filesystem");
218 PrintAndLogEx(INFO
, "Perso: EMV");
220 PrintAndLogEx(INFO
, "Perso: transaction supported");
224 static void CIPURSEPrintProfileInfo(uint8_t data
) {
226 PrintAndLogEx(INFO
, "Profile: L");
228 PrintAndLogEx(INFO
, "Profile: S");
230 PrintAndLogEx(INFO
, "Profile: T");
233 static void CIPURSEPrintManufacturerInfo(uint8_t data
) {
235 PrintAndLogEx(INFO
, "Manufacturer: n/a");
237 PrintAndLogEx(INFO
, "Manufacturer: %s", getTagInfo(data
)); // getTagInfo from cmfhf14a.h
240 void CIPURSEPrintInfoFile(uint8_t *data
, size_t len
) {
242 PrintAndLogEx(ERR
, "Info file length " _RED_("ERROR"));
246 PrintAndLogEx(INFO
, "------------ INFO ------------");
247 PrintAndLogEx(INFO
, "CIPURSE version %d revision %d", data
[0], data
[1]);
250 CIPURSEPrintPersoMode(data
[2]);
253 CIPURSEPrintProfileInfo(data
[3]);
256 CIPURSEPrintManufacturerInfo(data
[8]);
259 static void CIPURSEPrintFileDescriptor(uint8_t desc
) {
261 PrintAndLogEx(INFO
, "Binary file");
262 else if (desc
== 0x11)
263 PrintAndLogEx(INFO
, "Binary file with transactions");
264 else if (desc
== 0x02)
265 PrintAndLogEx(INFO
, "Linear record file");
266 else if (desc
== 0x12)
267 PrintAndLogEx(INFO
, "Linear record file with transactions");
268 else if (desc
== 0x06)
269 PrintAndLogEx(INFO
, "Cyclic record file");
270 else if (desc
== 0x16)
271 PrintAndLogEx(INFO
, "Cyclic record file with transactions");
272 else if (desc
== 0x1E)
273 PrintAndLogEx(INFO
, "Linear value-record file");
274 else if (desc
== 0x1F)
275 PrintAndLogEx(INFO
, "Linear value-record file with transactions");
277 PrintAndLogEx(INFO
, "Unknown file 0x%02x", desc
);
280 static void CIPURSEPrintKeyAttrib(uint8_t *attr
) {
281 PrintAndLogEx(INFO
, "-------- KEY ATTRIBUTES --------");
282 PrintAndLogEx(INFO
, "Additional info: 0x%02x", attr
[0]);
283 PrintAndLogEx(INFO
, "Key length: %d", attr
[1]);
284 PrintAndLogEx(INFO
, "Algorithm ID: 0x%02x", attr
[2]);
285 PrintAndLogEx(INFO
, "Security attr: 0x%02x", attr
[3]);
286 PrintAndLogEx(INFO
, "KVV: 0x%02x%02x%02x", attr
[4], attr
[5], attr
[6]);
287 PrintAndLogEx(INFO
, "-------------------------------");
290 void CIPURSEPrintFileAttr(uint8_t *fileAttr
, size_t len
) {
292 PrintAndLogEx(ERR
, "Attributes length " _RED_("ERROR"));
296 PrintAndLogEx(INFO
, "--------- FILE ATTRIBUTES ---------");
297 if (fileAttr
[0] == 0x38) {
298 PrintAndLogEx(INFO
, "Type: MF, ADF");
299 if (fileAttr
[1] == 0x00) {
300 PrintAndLogEx(INFO
, "Type: MF");
302 if ((fileAttr
[1] & 0xe0) == 0x00)
303 PrintAndLogEx(INFO
, "Type: Unknown");
304 if ((fileAttr
[1] & 0xe0) == 0x20)
305 PrintAndLogEx(INFO
, "Type: CIPURSE L");
306 if ((fileAttr
[1] & 0xe0) == 0x40)
307 PrintAndLogEx(INFO
, "Type: CIPURSE S");
308 if ((fileAttr
[1] & 0xe0) == 0x60)
309 PrintAndLogEx(INFO
, "Type: CIPURSE T");
310 if ((fileAttr
[1] & 0x02) == 0x00)
311 PrintAndLogEx(INFO
, "Autoselect on PxSE select OFF");
313 PrintAndLogEx(INFO
, "Autoselect on PxSE select ON");
314 if ((fileAttr
[1] & 0x01) == 0x00)
315 PrintAndLogEx(INFO
, "PxSE select returns FCPTemplate OFF");
317 PrintAndLogEx(INFO
, "PxSE select returns FCPTemplate ON");
320 PrintAndLogEx(INFO
, "File ID: 0x%02x%02x", fileAttr
[2], fileAttr
[3]);
322 PrintAndLogEx(INFO
, "Maximum number of custom EFs: %d", fileAttr
[4]);
323 PrintAndLogEx(INFO
, "Maximum number of EFs with SFID: %d", fileAttr
[5]);
324 uint8_t keyNum
= fileAttr
[6];
325 PrintAndLogEx(INFO
, "Keys assigned: %d", keyNum
);
328 PrintAndLogEx(INFO
, "SMR entries: %02x%02x", fileAttr
[7], fileAttr
[8]);
331 if (len
>= 10 + keyNum
+ 1) {
332 PrintAndLogEx(INFO
, "ART: %s", sprint_hex(&fileAttr
[9], keyNum
+ 1));
335 if (len
>= 11 + keyNum
+ 1 + keyNum
* 7) {
336 for (int i
= 0; i
< keyNum
; i
++) {
337 PrintAndLogEx(INFO
, "Key %d Attributes: %s", i
, sprint_hex(&fileAttr
[11 + keyNum
+ 1 + i
* 7], 7));
338 CIPURSEPrintKeyAttrib(&fileAttr
[11 + keyNum
+ 1 + i
* 7]);
342 if (fileAttr
[1] == 0x00) {
343 PrintAndLogEx(INFO
, "Total memory size: %d", (fileAttr
[len
- 6] << 16) + (fileAttr
[len
- 1] << 5) + fileAttr
[len
- 4]);
344 PrintAndLogEx(INFO
, "Free memory size: %d", (fileAttr
[len
- 3] << 16) + (fileAttr
[len
- 2] << 8) + fileAttr
[len
- 1]);
347 int ptr
= 11 + keyNum
+ 1 + keyNum
* 7;
349 PrintAndLogEx(INFO
, "TLV file control: %s", sprint_hex(&fileAttr
[ptr
], len
- ptr
));
352 PrintAndLogEx(INFO
, "Type: EF");
353 CIPURSEPrintFileDescriptor(fileAttr
[0]);
354 if (fileAttr
[1] == 0)
355 PrintAndLogEx(INFO
, "SFI: not assigned");
357 PrintAndLogEx(INFO
, "SFI: 0x%02x", fileAttr
[1]);
359 PrintAndLogEx(INFO
, "File ID: 0x%02x%02x", fileAttr
[2], fileAttr
[3]);
361 if (fileAttr
[0] == 0x01 || fileAttr
[0] == 0x11)
362 PrintAndLogEx(INFO
, "File size: %d", (fileAttr
[4] << 8) + fileAttr
[5]);
364 PrintAndLogEx(INFO
, "Record num: %d record size: %d", fileAttr
[4], fileAttr
[5]);
366 PrintAndLogEx(INFO
, "Keys assigned: %d", fileAttr
[6]);
369 PrintAndLogEx(INFO
, "SMR entries: %02x%02x", fileAttr
[7], fileAttr
[8]);
373 PrintAndLogEx(INFO
, "ART: %s", sprint_hex(&fileAttr
[9], len
- 9));
374 if (fileAttr
[6] + 1 != len
- 9)
375 PrintAndLogEx(WARNING
, "ART length is wrong");