//----------------------------------------------------------------------------- // Copyright (C) 2018 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. //----------------------------------------------------------------------------- // EMV json logic //----------------------------------------------------------------------------- #include "emvjson.h" #include #include "commonutil.h" // ARRAYLEN #include "ui.h" #include "util.h" #include "proxmark3.h" #include "emv_tags.h" #include "fileutils.h" #include "pm3_cmd.h" static const ApplicationDataElm ApplicationData[] = { {0x82, "AIP"}, {0x94, "AFL"}, {0x5A, "PAN"}, {0x5F34, "PANSeqNo"}, {0x5F24, "ExpirationDate"}, {0x5F25, "EffectiveDate"}, {0x5F28, "IssuerCountryCode"}, {0x50, "ApplicationLabel"}, {0x9F08, "VersionNumber"}, {0x9F42, "CurrencyCode"}, {0x5F2D, "LanguagePreference"}, {0x87, "PriorityIndicator"}, {0x9F36, "ATC"}, //Application Transaction Counter {0x5F20, "CardholderName"}, {0x9F38, "PDOL"}, {0x8C, "CDOL1"}, {0x8D, "CDOL2"}, {0x9F07, "AUC"}, // Application Usage Control {0x9F6C, "CTQ"}, {0x8E, "CVMList"}, {0x9F0D, "IACDefault"}, {0x9F0E, "IACDeny"}, {0x9F0F, "IACOnline"}, {0x8F, "CertificationAuthorityPublicKeyIndex"}, {0x9F32, "IssuerPublicKeyExponent"}, {0x92, "IssuerPublicKeyRemainder"}, {0x90, "IssuerPublicKeyCertificate"}, {0x9F47, "ICCPublicKeyExponent"}, {0x9F46, "ICCPublicKeyCertificate"}, {0x00, "end..."} }; const char *GetApplicationDataName(tlv_tag_t tag) { for (int i = 0; i < ARRAYLEN(ApplicationData); i++) if (ApplicationData[i].Tag == tag) return ApplicationData[i].Name; return NULL; } int JsonSaveJsonObject(json_t *root, const char *path, json_t *value) { json_error_t error; if (strlen(path) < 1) return 1; if (path[0] == '$') { if (json_path_set(root, path, value, 0, &error)) { PrintAndLogEx(ERR, "ERROR: can't set json path: ", error.text); return 2; } else { return 0; } } else { return json_object_set_new(root, path, value); } } int JsonSaveInt(json_t *root, const char *path, int value) { return JsonSaveJsonObject(root, path, json_integer(value)); } int JsonSaveStr(json_t *root, const char *path, const char *value) { return JsonSaveJsonObject(root, path, json_string(value)); }; int JsonSaveBufAsHexCompact(json_t *elm, const char *path, uint8_t *data, size_t datalen) { char *msg = sprint_hex_inrow(data, datalen); if (msg && strlen(msg) && msg[strlen(msg) - 1] == ' ') msg[strlen(msg) - 1] = '\0'; return JsonSaveStr(elm, path, msg); } int JsonSaveBufAsHex(json_t *elm, const char *path, uint8_t *data, size_t datalen) { char *msg = sprint_hex(data, datalen); if (msg && strlen(msg) && msg[strlen(msg) - 1] == ' ') msg[strlen(msg) - 1] = '\0'; return JsonSaveStr(elm, path, msg); } int JsonSaveHex(json_t *elm, const char *path, uint64_t data, int datalen) { uint8_t bdata[8] = {0}; int len = 0; if (!datalen) { for (uint64_t u = 0xffffffffffffffff; u; u = u << 8) { if (!(data & u)) { break; } len++; } if (!len) len = 1; } else { len = datalen; } num_to_bytes(data, len, bdata); return JsonSaveBufAsHex(elm, path, bdata, len); } int JsonSaveTLVValue(json_t *root, const char *path, struct tlvdb *tlvdbelm) { const struct tlv *tlvelm = tlvdb_get_tlv(tlvdbelm); if (tlvelm) return JsonSaveBufAsHex(root, path, (uint8_t *)tlvelm->value, tlvelm->len); else return 1; } int JsonSaveTLVElm(json_t *elm, const char *path, struct tlv *tlvelm, bool saveName, bool saveValue, bool saveAppDataLink) { json_error_t error; if (strlen(path) < 1 || !tlvelm) return 1; if (path[0] == '$') { json_t *obj = json_path_get(elm, path); if (!obj) { obj = json_object(); if (json_is_array(elm)) { if (json_array_append_new(elm, obj)) { PrintAndLogEx(ERR, "ERROR: can't append array: %s", path); return 2; } } else { if (json_path_set(elm, path, obj, 0, &error)) { PrintAndLogEx(ERR, "ERROR: can't set json path: ", error.text); return 2; } } } if (saveAppDataLink) { const char *AppDataName = GetApplicationDataName(tlvelm->tag); if (AppDataName) JsonSaveStr(obj, "appdata", AppDataName); } else { const char *name = emv_get_tag_name(tlvelm); if (saveName && name && strlen(name) > 0 && strncmp(name, "Unknown", 7)) JsonSaveStr(obj, "name", emv_get_tag_name(tlvelm)); JsonSaveHex(obj, "tag", tlvelm->tag, 0); if (saveValue) { JsonSaveHex(obj, "length", tlvelm->len, 0); JsonSaveBufAsHex(obj, "value", (uint8_t *)tlvelm->value, tlvelm->len); }; } } return 0; } int JsonSaveTLVTreeElm(json_t *elm, const char *path, struct tlvdb *tlvdbelm, bool saveName, bool saveValue, bool saveAppDataLink) { return JsonSaveTLVElm(elm, path, (struct tlv *)tlvdb_get_tlv(tlvdbelm), saveName, saveValue, saveAppDataLink); } int JsonSaveTLVTree(json_t *root, json_t *elm, const char *path, struct tlvdb *tlvdbelm) { struct tlvdb *tlvp = tlvdbelm; while (tlvp) { const struct tlv *tlvpelm = tlvdb_get_tlv(tlvp); const char *AppDataName = NULL; if (tlvpelm) AppDataName = GetApplicationDataName(tlvpelm->tag); if (AppDataName) { char appdatalink[200] = {0}; sprintf(appdatalink, "$.ApplicationData.%s", AppDataName); JsonSaveBufAsHex(root, appdatalink, (uint8_t *)tlvpelm->value, tlvpelm->len); } json_t *pelm = json_path_get(elm, path); if (pelm && json_is_array(pelm)) { json_t *appendelm = json_object(); json_array_append_new(pelm, appendelm); JsonSaveTLVTreeElm(appendelm, "$", tlvp, !AppDataName, !tlvdb_elm_get_children(tlvp), AppDataName); pelm = appendelm; } else { JsonSaveTLVTreeElm(elm, path, tlvp, !AppDataName, !tlvdb_elm_get_children(tlvp), AppDataName); pelm = json_path_get(elm, path); } if (tlvdb_elm_get_children(tlvp)) { // get path element if (!pelm) return 1; // check childs element and add it if not found json_t *chjson = json_path_get(pelm, "$.Childs"); if (!chjson) { json_object_set_new(pelm, "Childs", json_array()); chjson = json_path_get(pelm, "$.Childs"); } // check if (!json_is_array(chjson)) { PrintAndLogEx(ERR, "E->Internal logic error. `$.Childs` is not an array."); break; } // Recursion JsonSaveTLVTree(root, chjson, "$", tlvdb_elm_get_children(tlvp)); } tlvp = tlvdb_elm_get_next(tlvp); } return 0; } static bool HexToBuffer(const char *errormsg, const char *hexvalue, uint8_t *buffer, size_t maxbufferlen, size_t *bufferlen) { int buflen = 0; switch (param_gethex_to_eol(hexvalue, 0, buffer, maxbufferlen, &buflen)) { case 1: PrintAndLogEx(ERR, "%s Invalid HEX value.", errormsg); return false; case 2: PrintAndLogEx(ERR, "%s Hex value too large.", errormsg); return false; case 3: PrintAndLogEx(ERR, "%s Hex value must have even number of digits.", errormsg); return false; } if (buflen > maxbufferlen) { PrintAndLogEx(ERR, "%s HEX length (%d) more than %d", errormsg, (bufferlen) ? *bufferlen : -1, maxbufferlen); return false; } if (bufferlen) *bufferlen = buflen; return true; } int JsonLoadStr(json_t *root, const char *path, char *value) { if (!value) return 1; json_t *jelm = json_path_get((const json_t *)root, path); if (!jelm || !json_is_string(jelm)) return 2; const char *strval = json_string_value(jelm); if (!strval) return 1; memcpy(value, strval, strlen(strval)); return 0; } int JsonLoadBufAsHex(json_t *elm, const char *path, uint8_t *data, size_t maxbufferlen, size_t *datalen) { if (datalen) *datalen = 0; json_t *jelm = json_path_get((const json_t *)elm, path); if (!jelm || !json_is_string(jelm)) return 1; if (!HexToBuffer("ERROR load", json_string_value(jelm), data, maxbufferlen, datalen)) return 2; return 0; }; bool ParamLoadFromJson(struct tlvdb *tlv) { json_t *root; json_error_t error; if (!tlv) { PrintAndLogEx(ERR, "ERROR load params: tlv tree is NULL."); return false; } char *path; if (searchFile(&path, RESOURCES_SUBDIR, "emv_defparams", ".json") != PM3_SUCCESS) { return false; } root = json_load_file(path, 0, &error); free(path); if (!root) { PrintAndLogEx(ERR, "Load params: json error on line " _YELLOW_("%d") ": %s", error.line, error.text); return false; } if (!json_is_array(root)) { PrintAndLogEx(ERR, "Load params: Invalid json format. root must be array."); return false; } PrintAndLogEx(SUCCESS, "Load params: json(%d) " _GREEN_("OK"), json_array_size(root)); for (int i = 0; i < json_array_size(root); i++) { json_t *data, *jtag, *jlength, *jvalue; data = json_array_get(root, i); if (!json_is_object(data)) { PrintAndLogEx(ERR, "Load params: data [%d] is not an object", i + 1); json_decref(root); return false; } jtag = json_object_get(data, "tag"); if (!json_is_string(jtag)) { PrintAndLogEx(ERR, "Load params: data [%d] tag is not a string", i + 1); json_decref(root); return false; } const char *tlvTag = json_string_value(jtag); jvalue = json_object_get(data, "value"); if (!json_is_string(jvalue)) { PrintAndLogEx(ERR, "Load params: data [%d] value is not a string", i + 1); json_decref(root); return false; } const char *tlvValue = json_string_value(jvalue); jlength = json_object_get(data, "length"); if (!json_is_number(jlength)) { PrintAndLogEx(ERR, "Load params: data [%d] length is not a number", i + 1); json_decref(root); return false; } int tlvLength = json_integer_value(jlength); if (tlvLength > 250) { PrintAndLogEx(ERR, "Load params: data [%d] length more than 250", i + 1); json_decref(root); return false; } PrintAndLogEx(SUCCESS, "TLV param: %s[%d]=%s", tlvTag, tlvLength, tlvValue); uint8_t buf[251] = {0}; size_t buflen = 0; if (!HexToBuffer("TLV Error type:", tlvTag, buf, 4, &buflen)) { json_decref(root); return false; } tlv_tag_t tag = 0; for (int i = 0; i < buflen; i++) { tag = (tag << 8) | buf[i]; } if (!HexToBuffer("TLV Error value:", tlvValue, buf, sizeof(buf) - 1, &buflen)) { json_decref(root); return false; } if (buflen != tlvLength) { PrintAndLogEx(ERR, "Load params: data [%d] length of HEX must(%d) be identical to length in TLV param(%d)", i + 1, buflen, tlvLength); json_decref(root); return false; } tlvdb_change_or_add_node(tlv, tag, tlvLength, (const unsigned char *)buf); } json_decref(root); return true; }