| 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 | // asn.1 dumping |
| 9 | //----------------------------------------------------------------------------- |
| 10 | |
| 11 | #define _POSIX_C_SOURCE 200809L // need for strnlen() |
| 12 | |
| 13 | #include "asn1dump.h" |
| 14 | #include <ctype.h> |
| 15 | #include <stdlib.h> |
| 16 | #include <unistd.h> |
| 17 | #include <stdio.h> |
| 18 | #include <string.h> |
| 19 | #include <jansson.h> |
| 20 | #include <mbedtls/asn1.h> |
| 21 | #include <mbedtls/oid.h> |
| 22 | #include "emv/emv_tags.h" |
| 23 | #include "emv/dump.h" |
| 24 | #include "emv/emvjson.h" |
| 25 | #include "util.h" |
| 26 | #include "proxmark3.h" |
| 27 | |
| 28 | #define PRINT_INDENT(level) {for (int i = 0; i < (level); i++) fprintf(f, " ");} |
| 29 | |
| 30 | enum asn1_tag_t { |
| 31 | ASN1_TAG_GENERIC, |
| 32 | ASN1_TAG_BOOLEAN, |
| 33 | ASN1_TAG_INTEGER, |
| 34 | ASN1_TAG_STRING, |
| 35 | ASN1_TAG_OCTET_STRING, |
| 36 | ASN1_TAG_UTC_TIME, |
| 37 | ASN1_TAG_STR_TIME, |
| 38 | ASN1_TAG_OBJECT_ID, |
| 39 | }; |
| 40 | |
| 41 | struct asn1_tag { |
| 42 | tlv_tag_t tag; |
| 43 | char *name; |
| 44 | enum asn1_tag_t type; |
| 45 | const void *data; |
| 46 | }; |
| 47 | |
| 48 | static const struct asn1_tag asn1_tags[] = { |
| 49 | // internal |
| 50 | { 0x00 , "Unknown ???" }, |
| 51 | |
| 52 | // ASN.1 |
| 53 | { 0x01, "BOOLEAN", ASN1_TAG_BOOLEAN }, |
| 54 | { 0x02, "INTEGER", ASN1_TAG_INTEGER }, |
| 55 | { 0x03, "BIT STRING" }, |
| 56 | { 0x04, "OCTET STRING", ASN1_TAG_OCTET_STRING}, |
| 57 | { 0x05, "NULL" }, |
| 58 | { 0x06, "OBJECT IDENTIFIER", ASN1_TAG_OBJECT_ID }, |
| 59 | { 0x07, "OBJECT DESCRIPTOR" }, |
| 60 | { 0x08, "EXTERNAL" }, |
| 61 | { 0x09, "REAL" }, |
| 62 | { 0x0A, "ENUMERATED" }, |
| 63 | { 0x0B, "EMBEDDED_PDV" }, |
| 64 | { 0x0C, "UTF8String", ASN1_TAG_STRING }, |
| 65 | { 0x10, "SEQUENCE" }, |
| 66 | { 0x11, "SET" }, |
| 67 | { 0x12, "NumericString", ASN1_TAG_STRING }, |
| 68 | { 0x13, "PrintableString", ASN1_TAG_STRING }, |
| 69 | { 0x14, "T61String" }, |
| 70 | { 0x15, "VideotexString" }, |
| 71 | { 0x16, "IA5String" }, |
| 72 | { 0x17, "UTCTime", ASN1_TAG_UTC_TIME }, |
| 73 | { 0x18, "GeneralizedTime", ASN1_TAG_STR_TIME }, |
| 74 | { 0x19, "GraphicString" }, |
| 75 | { 0x1A, "VisibleString", ASN1_TAG_STRING }, |
| 76 | { 0x1B, "GeneralString", ASN1_TAG_STRING }, |
| 77 | { 0x1C, "UniversalString", ASN1_TAG_STRING }, |
| 78 | { 0x1E, "BMPString" }, |
| 79 | { 0x30, "SEQUENCE" }, |
| 80 | { 0x31, "SET" }, |
| 81 | { 0xa0, "[0]" }, |
| 82 | { 0xa1, "[1]" }, |
| 83 | { 0xa2, "[2]" }, |
| 84 | { 0xa3, "[3]" }, |
| 85 | { 0xa4, "[4]" }, |
| 86 | { 0xa5, "[5]" }, |
| 87 | }; |
| 88 | |
| 89 | static int asn1_sort_tag(tlv_tag_t tag) { |
| 90 | return (int)(tag >= 0x100 ? tag : tag << 8); |
| 91 | } |
| 92 | |
| 93 | static int asn1_tlv_compare(const void *a, const void *b) { |
| 94 | const struct tlv *tlv = a; |
| 95 | const struct asn1_tag *tag = b; |
| 96 | |
| 97 | return asn1_sort_tag(tlv->tag) - (asn1_sort_tag(tag->tag)); |
| 98 | } |
| 99 | |
| 100 | static const struct asn1_tag *asn1_get_tag(const struct tlv *tlv) { |
| 101 | struct asn1_tag *tag = bsearch(tlv, asn1_tags, sizeof(asn1_tags) / sizeof(asn1_tags[0]), |
| 102 | sizeof(asn1_tags[0]), asn1_tlv_compare); |
| 103 | |
| 104 | return tag ? tag : &asn1_tags[0]; |
| 105 | } |
| 106 | |
| 107 | static void asn1_tag_dump_str_time(const struct tlv *tlv, const struct asn1_tag *tag, FILE *f, int level, bool longyear, bool *needdump){ |
| 108 | int len = tlv->len; |
| 109 | *needdump = false; |
| 110 | |
| 111 | int startindx = longyear ? 4 : 2; |
| 112 | |
| 113 | if (len > 4) { |
| 114 | fprintf(f, "\tvalue: '"); |
| 115 | while (true) { |
| 116 | // year |
| 117 | if (!longyear) |
| 118 | fprintf(f, "20"); |
| 119 | fwrite(tlv->value, 1, longyear ? 4 : 2, f); |
| 120 | fprintf(f, "-"); |
| 121 | if (len < startindx + 2) |
| 122 | break; |
| 123 | // month |
| 124 | fwrite(&tlv->value[startindx], 1, 2, f); |
| 125 | fprintf(f, "-"); |
| 126 | if (len < startindx + 4) |
| 127 | break; |
| 128 | // day |
| 129 | fwrite(&tlv->value[startindx + 2], 1, 2, f); |
| 130 | fprintf(f, " "); |
| 131 | if (len < startindx + 6) |
| 132 | break; |
| 133 | // hour |
| 134 | fwrite(&tlv->value[startindx + 4], 1, 2, f); |
| 135 | fprintf(f, ":"); |
| 136 | if (len < startindx + 8) |
| 137 | break; |
| 138 | // min |
| 139 | fwrite(&tlv->value[startindx + 6], 1, 2, f); |
| 140 | fprintf(f, ":"); |
| 141 | if (len < startindx + 10) |
| 142 | break; |
| 143 | // sec |
| 144 | fwrite(&tlv->value[startindx + 8], 1, 2, f); |
| 145 | if (len < startindx + 11) |
| 146 | break; |
| 147 | // time zone |
| 148 | fprintf(f, " zone: %.*s", len - 10 - (longyear ? 4 : 2), &tlv->value[startindx + 10]); |
| 149 | |
| 150 | break; |
| 151 | } |
| 152 | fprintf(f, "'\n"); |
| 153 | } else { |
| 154 | fprintf(f, "\n"); |
| 155 | *needdump = true; |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | static void asn1_tag_dump_string(const struct tlv *tlv, const struct asn1_tag *tag, FILE *f, int level){ |
| 160 | fprintf(f, "\tvalue: '"); |
| 161 | fwrite(tlv->value, 1, tlv->len, f); |
| 162 | fprintf(f, "'\n"); |
| 163 | } |
| 164 | |
| 165 | static void asn1_tag_dump_octet_string(const struct tlv *tlv, const struct asn1_tag *tag, FILE *f, int level, bool *needdump){ |
| 166 | *needdump = false; |
| 167 | for (int i = 0; i < tlv->len; i++) |
| 168 | if (!isspace(tlv->value[i]) && !isprint(tlv->value[i])){ |
| 169 | *needdump = true; |
| 170 | break; |
| 171 | } |
| 172 | |
| 173 | if (*needdump) { |
| 174 | fprintf(f, "'\n"); |
| 175 | } else { |
| 176 | fprintf(f, "\t\t"); |
| 177 | asn1_tag_dump_string(tlv, tag, f, level); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | static unsigned long asn1_value_integer(const struct tlv *tlv, unsigned start, unsigned end) { |
| 182 | unsigned long ret = 0; |
| 183 | int i; |
| 184 | |
| 185 | if (end > tlv->len * 2) |
| 186 | return ret; |
| 187 | if (start >= end) |
| 188 | return ret; |
| 189 | |
| 190 | if (start & 1) { |
| 191 | ret += tlv->value[start/2] & 0xf; |
| 192 | i = start + 1; |
| 193 | } else |
| 194 | i = start; |
| 195 | |
| 196 | for (; i < end - 1; i += 2) { |
| 197 | ret *= 10; |
| 198 | ret += tlv->value[i/2] >> 4; |
| 199 | ret *= 10; |
| 200 | ret += tlv->value[i/2] & 0xf; |
| 201 | } |
| 202 | |
| 203 | if (end & 1) { |
| 204 | ret *= 10; |
| 205 | ret += tlv->value[end/2] >> 4; |
| 206 | } |
| 207 | |
| 208 | return ret; |
| 209 | } |
| 210 | |
| 211 | static void asn1_tag_dump_boolean(const struct tlv *tlv, const struct asn1_tag *tag, FILE *f, int level) { |
| 212 | PRINT_INDENT(level); |
| 213 | if (tlv->len > 0) { |
| 214 | fprintf(f, "\tvalue: %s\n", tlv->value[0]?"true":"false"); |
| 215 | } else { |
| 216 | fprintf(f, "n/a\n"); |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | static void asn1_tag_dump_integer(const struct tlv *tlv, const struct asn1_tag *tag, FILE *f, int level) { |
| 221 | PRINT_INDENT(level); |
| 222 | if (tlv->len == 4) { |
| 223 | int32_t val = 0; |
| 224 | for (int i = 0; i < tlv->len; i++) |
| 225 | val = (val << 8) + tlv->value[i]; |
| 226 | fprintf(f, "\tvalue4b: %d\n", val); |
| 227 | return; |
| 228 | } |
| 229 | fprintf(f, "\tvalue: %lu\n", asn1_value_integer(tlv, 0, tlv->len * 2)); |
| 230 | } |
| 231 | |
| 232 | static char *asn1_oid_description(const char *oid, bool with_group_desc) { |
| 233 | json_error_t error; |
| 234 | json_t *root = NULL; |
| 235 | char fname[300] = {0}; |
| 236 | static char res[300]; |
| 237 | memset(res, 0x00, sizeof(res)); |
| 238 | |
| 239 | strcpy(fname, get_my_executable_directory()); |
| 240 | strcat(fname, "crypto/oids.json"); |
| 241 | if (access(fname, F_OK) < 0) { |
| 242 | strcpy(fname, get_my_executable_directory()); |
| 243 | strcat(fname, "oids.json"); |
| 244 | if (access(fname, F_OK) < 0) { |
| 245 | goto error; // file not found |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | // load `oids.json` |
| 250 | root = json_load_file(fname, 0, &error); |
| 251 | |
| 252 | if (!root || !json_is_object(root)) { |
| 253 | goto error; |
| 254 | } |
| 255 | |
| 256 | json_t *elm = json_object_get(root, oid); |
| 257 | if (!elm) { |
| 258 | goto error; |
| 259 | } |
| 260 | |
| 261 | if (JsonLoadStr(elm, "$.d", res)) |
| 262 | goto error; |
| 263 | |
| 264 | char strext[300] = {0}; |
| 265 | if (!JsonLoadStr(elm, "$.c", strext)) { |
| 266 | strcat(res, " ("); |
| 267 | strcat(res, strext); |
| 268 | strcat(res, ")"); |
| 269 | } |
| 270 | |
| 271 | json_decref(root); |
| 272 | return res; |
| 273 | |
| 274 | error: |
| 275 | if (root) |
| 276 | json_decref(root); |
| 277 | return NULL; |
| 278 | } |
| 279 | |
| 280 | static void asn1_tag_dump_object_id(const struct tlv *tlv, const struct asn1_tag *tag, FILE *f, int level) { |
| 281 | PRINT_INDENT(level); |
| 282 | mbedtls_asn1_buf asn1_buf; |
| 283 | asn1_buf.len = tlv->len; |
| 284 | asn1_buf.p = (uint8_t *)tlv->value; |
| 285 | char pstr[300]; |
| 286 | mbedtls_oid_get_numeric_string(pstr, sizeof(pstr), &asn1_buf); |
| 287 | fprintf(f, " %s", pstr); |
| 288 | |
| 289 | char *jsondesc = asn1_oid_description(pstr, true); |
| 290 | if (jsondesc) { |
| 291 | fprintf(f, " - %s", jsondesc); |
| 292 | } else { |
| 293 | const char *ppstr; |
| 294 | mbedtls_oid_get_attr_short_name(&asn1_buf, &ppstr); |
| 295 | if (ppstr && strnlen(ppstr, 1)) { |
| 296 | fprintf(f, " (%s)\n", ppstr); |
| 297 | return; |
| 298 | } |
| 299 | mbedtls_oid_get_sig_alg_desc(&asn1_buf, &ppstr); |
| 300 | if (ppstr && strnlen(ppstr, 1)) { |
| 301 | fprintf(f, " (%s)\n", ppstr); |
| 302 | return; |
| 303 | } |
| 304 | mbedtls_oid_get_extended_key_usage(&asn1_buf, &ppstr); |
| 305 | if (ppstr && strnlen(ppstr, 1)) { |
| 306 | fprintf(f, " (%s)\n", ppstr); |
| 307 | return; |
| 308 | } |
| 309 | } |
| 310 | fprintf(f, "\n"); |
| 311 | } |
| 312 | |
| 313 | bool asn1_tag_dump(const struct tlv *tlv, FILE *f, int level, bool *candump) { |
| 314 | if (!tlv) { |
| 315 | fprintf(f, "NULL\n"); |
| 316 | return false; |
| 317 | } |
| 318 | |
| 319 | const struct asn1_tag *tag = asn1_get_tag(tlv); |
| 320 | |
| 321 | PRINT_INDENT(level); |
| 322 | fprintf(f, "--%2hx[%02zx] '%s':", tlv->tag, tlv->len, tag->name); |
| 323 | |
| 324 | switch (tag->type) { |
| 325 | case ASN1_TAG_GENERIC: |
| 326 | fprintf(f, "\n"); |
| 327 | break; |
| 328 | case ASN1_TAG_STRING: |
| 329 | asn1_tag_dump_string(tlv, tag, f, level); |
| 330 | *candump = false; |
| 331 | break; |
| 332 | case ASN1_TAG_OCTET_STRING: |
| 333 | asn1_tag_dump_octet_string(tlv, tag, f, level, candump); |
| 334 | break; |
| 335 | case ASN1_TAG_BOOLEAN: |
| 336 | asn1_tag_dump_boolean(tlv, tag, f, level); |
| 337 | *candump = false; |
| 338 | break; |
| 339 | case ASN1_TAG_INTEGER: |
| 340 | asn1_tag_dump_integer(tlv, tag, f, level); |
| 341 | *candump = false; |
| 342 | break; |
| 343 | case ASN1_TAG_UTC_TIME: |
| 344 | asn1_tag_dump_str_time(tlv, tag, f, level, false, candump); |
| 345 | break; |
| 346 | case ASN1_TAG_STR_TIME: |
| 347 | asn1_tag_dump_str_time(tlv, tag, f, level, true, candump); |
| 348 | break; |
| 349 | case ASN1_TAG_OBJECT_ID: |
| 350 | asn1_tag_dump_object_id(tlv, tag, f, level); |
| 351 | *candump = false; |
| 352 | break; |
| 353 | }; |
| 354 | |
| 355 | return true; |
| 356 | } |