Commit | Line | Data |
---|---|---|
95b697f0 OM |
1 | //----------------------------------------------------------------------------- |
2 | // Copyright (C) 2018 Merlok | |
3 | // | |
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 | |
6 | // the license. | |
7 | //----------------------------------------------------------------------------- | |
8 | // EMV json logic | |
9 | //----------------------------------------------------------------------------- | |
10 | ||
11 | #include "emvjson.h" | |
12 | #include <ctype.h> | |
13 | #include <stdio.h> | |
14 | #include <stdlib.h> | |
15 | #include <inttypes.h> | |
16 | #include <string.h> | |
17 | #include "util.h" | |
18 | #include "ui.h" | |
19 | #include "proxmark3.h" | |
20 | #include "emv_tags.h" | |
21 | ||
22 | static const ApplicationDataElm ApplicationData[] = { | |
23 | {0x82, "AIP"}, | |
24 | {0x94, "AFL"}, | |
25 | ||
26 | {0x5A, "PAN"}, | |
27 | {0x5F34, "PANSeqNo"}, | |
28 | {0x5F24, "ExpirationDate"}, | |
29 | {0x5F25, "EffectiveDate"}, | |
30 | {0x5F28, "IssuerCountryCode"}, | |
31 | ||
32 | {0x50, "ApplicationLabel"}, | |
33 | {0x9F08, "VersionNumber"}, | |
34 | {0x9F42, "CurrencyCode"}, | |
35 | {0x5F2D, "LanguagePreference"}, | |
36 | {0x87, "PriorityIndicator"}, | |
37 | {0x9F36, "ATC"}, //Application Transaction Counter | |
38 | ||
39 | {0x5F20, "CardholderName"}, | |
40 | ||
41 | {0x9F38, "PDOL"}, | |
42 | {0x8C, "CDOL1"}, | |
43 | {0x8D, "CDOL2"}, | |
44 | ||
45 | {0x9F07, "AUC"}, // Application Usage Control | |
46 | {0x9F6C, "CTQ"}, | |
47 | {0x8E, "CVMList"}, | |
48 | {0x9F0D, "IACDefault"}, | |
49 | {0x9F0E, "IACDeny"}, | |
50 | {0x9F0F, "IACOnline"}, | |
51 | ||
52 | {0x8F, "CertificationAuthorityPublicKeyIndex"}, | |
53 | {0x9F32, "IssuerPublicKeyExponent"}, | |
54 | {0x92, "IssuerPublicKeyRemainder"}, | |
55 | {0x90, "IssuerPublicKeyCertificate"}, | |
56 | {0x9F47, "ICCPublicKeyExponent"}, | |
57 | {0x9F46, "ICCPublicKeyCertificate"}, | |
58 | ||
59 | {0x00, "end..."} | |
60 | }; | |
61 | int ApplicationDataLen = sizeof(ApplicationData) / sizeof(ApplicationDataElm); | |
62 | ||
63 | char* GetApplicationDataName(tlv_tag_t tag) { | |
64 | for (int i = 0; i < ApplicationDataLen; i++) | |
65 | if (ApplicationData[i].Tag == tag) | |
66 | return ApplicationData[i].Name; | |
67 | ||
68 | return NULL; | |
69 | } | |
70 | ||
39cc1c87 | 71 | int JsonSaveJsonObject(json_t *root, char *path, json_t *value) { |
95b697f0 OM |
72 | json_error_t error; |
73 | ||
74 | if (strlen(path) < 1) | |
75 | return 1; | |
76 | ||
77 | if (path[0] == '$') { | |
39cc1c87 | 78 | if (json_path_set(root, path, value, 0, &error)) { |
95b697f0 OM |
79 | PrintAndLog("ERROR: can't set json path: ", error.text); |
80 | return 2; | |
81 | } else { | |
82 | return 0; | |
83 | } | |
84 | } else { | |
39cc1c87 | 85 | return json_object_set_new(root, path, value); |
95b697f0 | 86 | } |
39cc1c87 OM |
87 | } |
88 | ||
89 | int JsonSaveInt(json_t *root, char *path, int value) { | |
90 | return JsonSaveJsonObject(root, path, json_integer(value)); | |
91 | } | |
92 | ||
93 | int JsonSaveStr(json_t *root, char *path, char *value) { | |
94 | return JsonSaveJsonObject(root, path, json_string(value)); | |
95b697f0 OM |
95 | }; |
96 | ||
39cc1c87 OM |
97 | int JsonSaveBufAsHexCompact(json_t *elm, char *path, uint8_t *data, size_t datalen) { |
98 | char * msg = sprint_hex_inrow(data, datalen); | |
99 | if (msg && strlen(msg) && msg[strlen(msg) - 1] == ' ') | |
100 | msg[strlen(msg) - 1] = '\0'; | |
101 | ||
102 | return JsonSaveStr(elm, path, msg); | |
103 | } | |
104 | ||
95b697f0 OM |
105 | int JsonSaveBufAsHex(json_t *elm, char *path, uint8_t *data, size_t datalen) { |
106 | char * msg = sprint_hex(data, datalen); | |
107 | if (msg && strlen(msg) && msg[strlen(msg) - 1] == ' ') | |
108 | msg[strlen(msg) - 1] = '\0'; | |
109 | ||
110 | return JsonSaveStr(elm, path, msg); | |
111 | } | |
112 | ||
113 | int JsonSaveHex(json_t *elm, char *path, uint64_t data, int datalen) { | |
114 | uint8_t bdata[8] = {0}; | |
115 | int len = 0; | |
116 | if (!datalen) { | |
117 | for (uint64_t u = 0xffffffffffffffff; u; u = u << 8) { | |
118 | if (!(data & u)) { | |
119 | break; | |
120 | } | |
121 | len++; | |
122 | } | |
123 | if (!len) | |
124 | len = 1; | |
125 | } else { | |
126 | len = datalen; | |
127 | } | |
128 | num_to_bytes(data, len, bdata); | |
129 | ||
130 | return JsonSaveBufAsHex(elm, path, bdata, len); | |
131 | } | |
132 | ||
133 | int JsonSaveTLVValue(json_t *root, char *path, struct tlvdb *tlvdbelm) { | |
134 | const struct tlv *tlvelm = tlvdb_get_tlv(tlvdbelm); | |
135 | if (tlvelm) | |
136 | return JsonSaveBufAsHex(root, path, (uint8_t *)tlvelm->value, tlvelm->len); | |
137 | else | |
138 | return 1; | |
139 | } | |
140 | ||
141 | int JsonSaveTLVElm(json_t *elm, char *path, struct tlv *tlvelm, bool saveName, bool saveValue, bool saveAppDataLink) { | |
142 | json_error_t error; | |
143 | ||
144 | if (strlen(path) < 1 || !tlvelm) | |
145 | return 1; | |
146 | ||
147 | if (path[0] == '$') { | |
148 | ||
149 | json_t *obj = json_path_get(elm, path); | |
150 | if (!obj) { | |
151 | obj = json_object(); | |
152 | ||
153 | if (json_is_array(elm)) { | |
154 | if (json_array_append_new(elm, obj)) { | |
155 | PrintAndLog("ERROR: can't append array: %s", path); | |
156 | return 2; | |
157 | } | |
158 | } else { | |
159 | if (json_path_set(elm, path, obj, 0, &error)) { | |
160 | PrintAndLog("ERROR: can't set json path: ", error.text); | |
161 | return 2; | |
162 | } | |
163 | } | |
164 | } | |
165 | ||
166 | if (saveAppDataLink) { | |
167 | char * AppDataName = GetApplicationDataName(tlvelm->tag); | |
168 | if (AppDataName) | |
169 | JsonSaveStr(obj, "appdata", AppDataName); | |
170 | } else { | |
171 | char * name = emv_get_tag_name(tlvelm); | |
172 | if (saveName && name && strlen(name) > 0 && strncmp(name, "Unknown", 7)) | |
173 | JsonSaveStr(obj, "name", emv_get_tag_name(tlvelm)); | |
174 | JsonSaveHex(obj, "tag", tlvelm->tag, 0); | |
175 | if (saveValue) { | |
176 | JsonSaveHex(obj, "length", tlvelm->len, 0); | |
177 | JsonSaveBufAsHex(obj, "value", (uint8_t *)tlvelm->value, tlvelm->len); | |
178 | }; | |
179 | } | |
180 | } | |
181 | ||
182 | return 0; | |
183 | } | |
184 | ||
185 | int JsonSaveTLVTreeElm(json_t *elm, char *path, struct tlvdb *tlvdbelm, bool saveName, bool saveValue, bool saveAppDataLink) { | |
186 | return JsonSaveTLVElm(elm, path, (struct tlv *)tlvdb_get_tlv(tlvdbelm), saveName, saveValue, saveAppDataLink); | |
187 | } | |
188 | ||
189 | int JsonSaveTLVTree(json_t *root, json_t *elm, char *path, struct tlvdb *tlvdbelm) { | |
190 | struct tlvdb *tlvp = tlvdbelm; | |
191 | while (tlvp) { | |
192 | const struct tlv * tlvpelm = tlvdb_get_tlv(tlvp); | |
193 | char * AppDataName = NULL; | |
194 | if (tlvpelm) | |
195 | AppDataName = GetApplicationDataName(tlvpelm->tag); | |
196 | ||
197 | if (AppDataName) { | |
198 | char appdatalink[200] = {0}; | |
199 | sprintf(appdatalink, "$.ApplicationData.%s", AppDataName); | |
200 | JsonSaveBufAsHex(root, appdatalink, (uint8_t *)tlvpelm->value, tlvpelm->len); | |
201 | } | |
202 | ||
203 | json_t *pelm = json_path_get(elm, path); | |
204 | if (pelm && json_is_array(pelm)) { | |
205 | json_t *appendelm = json_object(); | |
206 | json_array_append_new(pelm, appendelm); | |
207 | JsonSaveTLVTreeElm(appendelm, "$", tlvp, !AppDataName, !tlvdb_elm_get_children(tlvp), AppDataName); | |
208 | pelm = appendelm; | |
209 | } else { | |
210 | JsonSaveTLVTreeElm(elm, path, tlvp, !AppDataName, !tlvdb_elm_get_children(tlvp), AppDataName); | |
211 | pelm = json_path_get(elm, path); | |
212 | } | |
213 | ||
214 | if (tlvdb_elm_get_children(tlvp)) { | |
215 | // get path element | |
216 | if(!pelm) | |
217 | return 1; | |
218 | ||
219 | // check childs element and add it if not found | |
220 | json_t *chjson = json_path_get(pelm, "$.Childs"); | |
221 | if (!chjson) { | |
222 | json_object_set_new(pelm, "Childs", json_array()); | |
223 | ||
224 | chjson = json_path_get(pelm, "$.Childs"); | |
225 | } | |
226 | ||
227 | // check | |
228 | if (!json_is_array(chjson)) { | |
229 | PrintAndLog("E->Internal logic error. `$.Childs` is not an array."); | |
230 | break; | |
231 | } | |
232 | ||
233 | // Recursion | |
234 | JsonSaveTLVTree(root, chjson, "$", tlvdb_elm_get_children(tlvp)); | |
235 | } | |
236 | ||
237 | tlvp = tlvdb_elm_get_next(tlvp); | |
238 | } | |
239 | return 0; | |
240 | } | |
241 | ||
242 | bool HexToBuffer(const char *errormsg, const char *hexvalue, uint8_t * buffer, size_t maxbufferlen, size_t *bufferlen) { | |
243 | int buflen = 0; | |
244 | ||
245 | switch(param_gethex_to_eol(hexvalue, 0, buffer, maxbufferlen, &buflen)) { | |
246 | case 1: | |
247 | PrintAndLog("%s Invalid HEX value.", errormsg); | |
248 | return false; | |
249 | case 2: | |
250 | PrintAndLog("%s Hex value too large.", errormsg); | |
251 | return false; | |
252 | case 3: | |
253 | PrintAndLog("%s Hex value must have even number of digits.", errormsg); | |
254 | return false; | |
255 | } | |
256 | ||
257 | if (buflen > maxbufferlen) { | |
258 | PrintAndLog("%s HEX length (%d) more than %d", errormsg, *bufferlen, maxbufferlen); | |
259 | return false; | |
260 | } | |
261 | ||
262 | *bufferlen = buflen; | |
263 | ||
264 | return true; | |
265 | } | |
266 | ||
6b882a39 OM |
267 | int JsonLoadStr(json_t *root, char *path, char *value) { |
268 | if (!value) | |
269 | return 1; | |
270 | ||
271 | json_t *jelm = json_path_get((const json_t *)root, path); | |
272 | if (!jelm || !json_is_string(jelm)) | |
273 | return 2; | |
274 | ||
275 | const char * strval = json_string_value(jelm); | |
276 | if (!strval) | |
277 | return 1; | |
278 | ||
279 | memcpy(value, strval, strlen(strval)); | |
280 | ||
281 | return 0; | |
282 | } | |
283 | ||
39cc1c87 OM |
284 | int JsonLoadBufAsHex(json_t *elm, char *path, uint8_t *data, size_t maxbufferlen, size_t *datalen) { |
285 | if (datalen) | |
286 | *datalen = 0; | |
287 | ||
288 | json_t *jelm = json_path_get((const json_t *)elm, path); | |
289 | if (!jelm || !json_is_string(jelm)) | |
290 | return 1; | |
291 | ||
292 | if (!HexToBuffer("ERROR load", json_string_value(jelm), data, maxbufferlen, datalen)) | |
293 | return 2; | |
294 | ||
295 | return 0; | |
296 | }; | |
297 | ||
95b697f0 OM |
298 | bool ParamLoadFromJson(struct tlvdb *tlv) { |
299 | json_t *root; | |
300 | json_error_t error; | |
301 | ||
302 | if (!tlv) { | |
303 | PrintAndLog("ERROR load params: tlv tree is NULL."); | |
304 | return false; | |
305 | } | |
306 | ||
307 | // current path + file name | |
308 | const char *relfname = "emv/defparams.json"; | |
309 | char fname[strlen(get_my_executable_directory()) + strlen(relfname) + 1]; | |
310 | strcpy(fname, get_my_executable_directory()); | |
311 | strcat(fname, relfname); | |
312 | ||
313 | root = json_load_file(fname, 0, &error); | |
314 | if (!root) { | |
315 | PrintAndLog("Load params: json error on line %d: %s", error.line, error.text); | |
316 | return false; | |
317 | } | |
318 | ||
319 | if (!json_is_array(root)) { | |
320 | PrintAndLog("Load params: Invalid json format. root must be array."); | |
321 | return false; | |
322 | } | |
323 | ||
324 | PrintAndLog("Load params: json(%d) OK", json_array_size(root)); | |
325 | ||
326 | for(int i = 0; i < json_array_size(root); i++) { | |
327 | json_t *data, *jtag, *jlength, *jvalue; | |
328 | ||
329 | data = json_array_get(root, i); | |
330 | if(!json_is_object(data)) | |
331 | { | |
332 | PrintAndLog("Load params: data [%d] is not an object", i + 1); | |
333 | json_decref(root); | |
334 | return false; | |
335 | } | |
336 | ||
337 | jtag = json_object_get(data, "tag"); | |
338 | if(!json_is_string(jtag)) | |
339 | { | |
340 | PrintAndLog("Load params: data [%d] tag is not a string", i + 1); | |
341 | json_decref(root); | |
342 | return false; | |
343 | } | |
344 | const char *tlvTag = json_string_value(jtag); | |
345 | ||
346 | jvalue = json_object_get(data, "value"); | |
347 | if(!json_is_string(jvalue)) | |
348 | { | |
349 | PrintAndLog("Load params: data [%d] value is not a string", i + 1); | |
350 | json_decref(root); | |
351 | return false; | |
352 | } | |
353 | const char *tlvValue = json_string_value(jvalue); | |
354 | ||
355 | jlength = json_object_get(data, "length"); | |
356 | if(!json_is_number(jlength)) | |
357 | { | |
358 | PrintAndLog("Load params: data [%d] length is not a number", i + 1); | |
359 | json_decref(root); | |
360 | return false; | |
361 | } | |
362 | ||
363 | int tlvLength = json_integer_value(jlength); | |
364 | if (tlvLength > 250) { | |
365 | PrintAndLog("Load params: data [%d] length more than 250", i + 1); | |
366 | json_decref(root); | |
367 | return false; | |
368 | } | |
369 | ||
370 | PrintAndLog("TLV param: %s[%d]=%s", tlvTag, tlvLength, tlvValue); | |
371 | uint8_t buf[251] = {0}; | |
372 | size_t buflen = 0; | |
373 | ||
374 | // here max length must be 4, but now tlv_tag_t is 2-byte var. so let it be 2 by now... TODO: needs refactoring tlv_tag_t... | |
375 | if (!HexToBuffer("TLV Error type:", tlvTag, buf, 2, &buflen)) { | |
376 | json_decref(root); | |
377 | return false; | |
378 | } | |
379 | tlv_tag_t tag = 0; | |
380 | for (int i = 0; i < buflen; i++) { | |
381 | tag = (tag << 8) + buf[i]; | |
382 | } | |
383 | ||
384 | if (!HexToBuffer("TLV Error value:", tlvValue, buf, sizeof(buf) - 1, &buflen)) { | |
385 | json_decref(root); | |
386 | return false; | |
387 | } | |
388 | ||
389 | if (buflen != tlvLength) { | |
390 | PrintAndLog("Load params: data [%d] length of HEX must(%d) be identical to length in TLV param(%d)", i + 1, buflen, tlvLength); | |
391 | json_decref(root); | |
392 | return false; | |
393 | } | |
394 | ||
395 | tlvdb_change_or_add_node(tlv, tag, tlvLength, (const unsigned char *)buf); | |
396 | } | |
397 | ||
398 | json_decref(root); | |
399 | ||
400 | return true; | |
401 | } | |
402 |