//----------------------------------------------------------------------------- // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // See LICENSE.txt for the text of the license. //----------------------------------------------------------------------------- // PIV commands //----------------------------------------------------------------------------- #include "cmdpiv.h" #include "comms.h" // DropField #include "cmdsmartcard.h" // smart_select #include "cmdtrace.h" #include "cliparser.h" #include "cmdparser.h" #include "commonutil.h" // Mem[LB]eToUintXByte #include "emv/tlv.h" #include "proxmark3.h" #include "cmdhf14a.h" #include "fileutils.h" #include "crypto/asn1utils.h" #include "protocols.h" static int CmdHelp(const char *Cmd); static uint8_t PIV_APPLET[9] = "\xA0\x00\x00\x03\x08\x00\x00\x10\x00"; enum piv_condition_t { PIV_MANDATORY, PIV_CONDITIONAL, PIV_OPTIONAL, PIV_INVALID = 0xff, }; struct piv_container { uint32_t id; const uint8_t *tlv_tag; // tag is between 1 and 3 bytes. size_t len; // length of the hex-form if the tag (i.e. twice the byte size) for pretty printing enum piv_condition_t cond; const char *name; }; #define PIV_TAG_ID(x) ((const uint8_t *)(x)) #define PIV_CONTAINER_FINISH { (~0), NULL, 0, PIV_INVALID, NULL } // Source: SP800-73-4, Annex A // https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-73-4.pdf static const struct piv_container PIV_CONTAINERS[] = { {0xDB00, PIV_TAG_ID("\x5F\xC1\x07"), 3, PIV_MANDATORY, "Card Capability Container"}, {0x3000, PIV_TAG_ID("\x5F\xC1\x02"), 3, PIV_MANDATORY, "Card Holder Unique Identifier"}, {0x0101, PIV_TAG_ID("\x5F\xC1\x05"), 3, PIV_MANDATORY, "X.509 Certificate for PIV Authentication (key ref 9A)"}, {0x6010, PIV_TAG_ID("\x5F\xC1\x03"), 3, PIV_MANDATORY, "Cardholder Fingerprints"}, {0x9000, PIV_TAG_ID("\x5F\xC1\x06"), 3, PIV_MANDATORY, "Security Object"}, {0x6030, PIV_TAG_ID("\x5F\xC1\x08"), 3, PIV_MANDATORY, "Cardholder Facial Image"}, {0x0500, PIV_TAG_ID("\x5F\xC1\x01"), 3, PIV_MANDATORY, "X.509 Certificate for Card Authentication (key ref 9E)"}, {0x0100, PIV_TAG_ID("\x5F\xC1\x0A"), 3, PIV_CONDITIONAL, "X.509 Certificate for Digital Signature (key ref 9C)"}, {0x0102, PIV_TAG_ID("\x5F\xC1\x0B"), 3, PIV_CONDITIONAL, "X.509 Certificate for Key Management (key ref 9D)"}, {0x3001, PIV_TAG_ID("\x5F\xC1\x09"), 3, PIV_OPTIONAL, "Printed Information"}, {0x6050, PIV_TAG_ID("\x7E"), 1, PIV_OPTIONAL, "Discovery Object"}, {0x6060, PIV_TAG_ID("\x5F\xC1\x0C"), 3, PIV_OPTIONAL, "Key History Object"}, {0x1001, PIV_TAG_ID("\x5F\xC1\x0D"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 1 (key ref 82)"}, {0x1002, PIV_TAG_ID("\x5F\xC1\x0E"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 2 (key ref 83)"}, {0x1003, PIV_TAG_ID("\x5F\xC1\x0F"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 3 (key ref 84)"}, {0x1004, PIV_TAG_ID("\x5F\xC1\x10"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 4 (key ref 85)"}, {0x1005, PIV_TAG_ID("\x5F\xC1\x11"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 5 (key ref 86)"}, {0x1006, PIV_TAG_ID("\x5F\xC1\x12"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 6 (key ref 87)"}, {0x1007, PIV_TAG_ID("\x5F\xC1\x13"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 7 (key ref 88)"}, {0x1008, PIV_TAG_ID("\x5F\xC1\x14"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 8 (key ref 89)"}, {0x1009, PIV_TAG_ID("\x5F\xC1\x15"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 9 (key ref 8A)"}, {0x100A, PIV_TAG_ID("\x5F\xC1\x16"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 10 (key ref 8B)"}, {0x100B, PIV_TAG_ID("\x5F\xC1\x17"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 11 (key ref 8C)"}, {0x100C, PIV_TAG_ID("\x5F\xC1\x18"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 12 (key ref 8D)"}, {0x100D, PIV_TAG_ID("\x5F\xC1\x19"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 13 (key ref 8E)"}, {0x100E, PIV_TAG_ID("\x5F\xC1\x1A"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 14 (key ref 8F)"}, {0x100F, PIV_TAG_ID("\x5F\xC1\x1B"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 15 (key ref 90)"}, {0x1010, PIV_TAG_ID("\x5F\xC1\x1C"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 16 (key ref 91)"}, {0x1011, PIV_TAG_ID("\x5F\xC1\x1D"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 17 (key ref 92)"}, {0x1012, PIV_TAG_ID("\x5F\xC1\x1E"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 18 (key ref 93)"}, {0x1013, PIV_TAG_ID("\x5F\xC1\x1F"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 19 (key ref 94)"}, {0x1014, PIV_TAG_ID("\x5F\xC1\x20"), 3, PIV_OPTIONAL, "Retired X.509 Certificate for Key Management 20 (key ref 95)"}, {0x1015, PIV_TAG_ID("\x5F\xC1\x21"), 3, PIV_OPTIONAL, "Cardholder Iris Images"}, {0x1016, PIV_TAG_ID("\x7F\x61"), 2, PIV_OPTIONAL, "Biometric Information Templates Group Template"}, {0x1017, PIV_TAG_ID("\x5F\xC1\x22"), 3, PIV_OPTIONAL, "Secure Messaging Certificate Signer"}, {0x1018, PIV_TAG_ID("\x5F\xC1\x23"), 3, PIV_OPTIONAL, "Pairing Code Reference Data Container"}, PIV_CONTAINER_FINISH, }; enum piv_tag_t { PIV_TAG_GENERIC, PIV_TAG_HEXDUMP, PIV_TAG_STRING, PIV_TAG_PRINTSTR, PIV_TAG_NUMERIC, PIV_TAG_YYYYMMDD, PIV_TAG_ENUM, PIV_TAG_TLV, PIV_TAG_GUID, PIV_TAG_CERT, PIV_TAG_FASCN, }; struct piv_tag { tlv_tag_t tag; const char *name; enum piv_tag_t type; const void *data; }; struct piv_tag_enum { unsigned long value; const char *name; }; #define PIV_ENUM_FINISH { (~0), NULL } // From table 6-2 in SP800-78 specification static const struct piv_tag_enum PIV_CRYPTO_ALG[] = { {0x00, "3 Key 3DES - ECB"}, {0x03, "3 Key 3DES - ECB"}, // Not a typo, 2 identifiers for the same algorithm {0x06, "RSA 1024 bit"}, {0x07, "RSA 2048 bit"}, {0x08, "AES-128 ECB"}, {0x0A, "AES-192 ECB"}, {0x0C, "AES-256 ECB"}, {0x11, "ECC P-256"}, {0x14, "ECC P-384"}, {0x27, "Cipher Suite 2"}, {0x2E, "Cipher Suite 7"}, PIV_ENUM_FINISH, }; static const struct piv_tag_enum PIV_CERT_INFO[] = { {0x00, "Uncompressed"}, {0x01, "GZIP Compressed"}, PIV_ENUM_FINISH, }; static const struct piv_tag piv_tags[] = { { 0x00, "Unknown ???", PIV_TAG_HEXDUMP, NULL }, { 0x01, "Name", PIV_TAG_PRINTSTR, NULL }, { 0x02, "Employee Affiliation", PIV_TAG_PRINTSTR, NULL }, { 0x04, "Expiry Date", PIV_TAG_PRINTSTR, NULL }, { 0x05, "Agency Card Serial Number", PIV_TAG_PRINTSTR, NULL }, { 0x06, "Issuer identification", PIV_TAG_PRINTSTR, NULL }, { 0x07, "Organization Affiliation (Line 1)", PIV_TAG_PRINTSTR, NULL }, { 0x08, "Organization Affiliation (Line 2)", PIV_TAG_PRINTSTR, NULL }, { 0x30, "FASC-N", PIV_TAG_FASCN, NULL }, { 0x32, "Organizational Identifier [deprecated]", PIV_TAG_HEXDUMP, NULL }, { 0x33, "DUNS [deprecated]", PIV_TAG_HEXDUMP, NULL }, { 0x34, "GUID", PIV_TAG_GUID, NULL }, { 0x35, "Expiry Date", PIV_TAG_YYYYMMDD, NULL }, { 0x36, "Cardholder UUID", PIV_TAG_GUID, NULL }, { 0x3d, "Authentication Key Map", PIV_TAG_HEXDUMP, NULL }, { 0x3e, "Issuer Asymmetric Signature", PIV_TAG_CERT, NULL }, { 0x4f, "Application Identifier (AID)", PIV_TAG_STRING, NULL }, { 0x50, "Application Label", PIV_TAG_PRINTSTR, NULL }, { 0x53, "Discretionary data (or template)", PIV_TAG_TLV, NULL }, { 0x5f2f, "PIN Usage Policy", PIV_TAG_HEXDUMP, NULL }, { 0x5f50, "Issuer URL", PIV_TAG_PRINTSTR, NULL }, { 0x61, "Application Property Template", PIV_TAG_GENERIC, NULL }, { 0x70, "Certificate", PIV_TAG_CERT, NULL }, { 0x71, "CertInfo", PIV_TAG_ENUM, PIV_CERT_INFO }, { 0x72, "MSCUID [deprecated]", PIV_TAG_HEXDUMP, NULL }, { 0x79, "Coexistent tag allocation authority", PIV_TAG_HEXDUMP, NULL }, { 0x7f21, "Intermediate CVC", PIV_TAG_HEXDUMP, NULL }, { 0x7f60, "Biometric Information Template", PIV_TAG_GENERIC, NULL }, { 0x80, "Cryptographic algorithm identifier", PIV_TAG_ENUM, PIV_CRYPTO_ALG }, { 0x99, "Pairing Code", PIV_TAG_PRINTSTR, NULL }, { 0xac, "Cryptographic algorithms supported", PIV_TAG_GENERIC, NULL }, { 0xb4, "Security Object Buffer (deprecated)", PIV_TAG_GENERIC, NULL }, { 0xba, "Mapping of DG to Container ID", PIV_TAG_HEXDUMP, NULL }, { 0xbb, "Security Object", PIV_TAG_CERT, NULL }, { 0xbc, "Fingerprint I & II or Image for Visual Verification", PIV_TAG_GENERIC, NULL }, { 0xc1, "keysWithOnCardCerts", PIV_TAG_NUMERIC, NULL }, { 0xc2, "keysWithOffCardCerts", PIV_TAG_NUMERIC, NULL }, { 0xe3, "Extended Application CardURL [deprecated]", PIV_TAG_GENERIC, NULL }, { 0xee, "Buffer Length [deprecated]", PIV_TAG_NUMERIC, NULL }, { 0xf0, "Card Identifier", PIV_TAG_STRING, NULL }, { 0xf1, "Capability Container version number", PIV_TAG_NUMERIC, NULL }, { 0xf2, "Capability Grammar version number", PIV_TAG_NUMERIC, NULL }, { 0xf3, "Application Card URL", PIV_TAG_PRINTSTR, NULL }, { 0xf4, "PKCS#15", PIV_TAG_NUMERIC, NULL }, { 0xf5, "Registered Data Model Number", PIV_TAG_NUMERIC, NULL }, { 0xf6, "Access Control Rule Table", PIV_TAG_HEXDUMP, NULL }, { 0xf7, "Card APDUs", PIV_TAG_GENERIC, NULL }, { 0xfa, "Redirection Tag", PIV_TAG_GENERIC, NULL }, { 0xfb, "Capability Tuples (CT)", PIV_TAG_GENERIC, NULL }, { 0xfc, "Status Tuples (ST)", PIV_TAG_GENERIC, NULL }, { 0xfd, "Next CCC", PIV_TAG_GENERIC, NULL }, { 0xfe, "Error Detection Code", PIV_TAG_GENERIC, NULL }, }; struct guid { uint32_t part1; uint16_t part2; uint16_t part3; uint8_t data[8]; }; static void parse_guid(const uint8_t *data, struct guid *guid) { if (guid == NULL) { return; } size_t ofs = 0; guid->part1 = MemBeToUint4byte(&data[ofs]); ofs += sizeof(uint32_t); guid->part2 = MemBeToUint2byte(&data[ofs]); ofs += sizeof(uint16_t); guid->part3 = MemBeToUint2byte(&data[ofs]); ofs += sizeof(uint16_t); for (size_t i = 0; i < sizeof(guid->data); i++) { guid->data[i] = data[ofs + i]; } } static void piv_print_cb(void *data, const struct tlv *tlv, int level, bool is_leaf); static bool piv_tag_dump(const struct tlv *tlv, int level); static void PrintChannel(Iso7816CommandChannel channel) { switch (channel) { case CC_CONTACTLESS: PrintAndLogEx(INFO, "Selected channel... " _GREEN_("CONTACTLESS (T=CL)")); break; case CC_CONTACT: PrintAndLogEx(INFO, "Selected channel... " _GREEN_("CONTACT")); break; } } static int piv_sort_tag(tlv_tag_t tag) { return (int)(tag >= 0x100 ? tag : tag << 8); } static int piv_tlv_compare(const void *a, const void *b) { const struct tlv *tlv = a; const struct piv_tag *tag = b; return piv_sort_tag(tlv->tag) - (piv_sort_tag(tag->tag)); } static const struct piv_tag *piv_get_tag(const struct tlv *tlv) { const struct piv_tag *tag = bsearch(tlv, piv_tags, ARRAYLEN(piv_tags), sizeof(piv_tags[0]), piv_tlv_compare); return tag != NULL ? tag : &piv_tags[0]; } static unsigned long piv_value_numeric(const struct tlv *tlv, unsigned start, unsigned end) { unsigned long ret = 0; int i; if (end > tlv->len * 2) return ret; if (start >= end) return ret; if (start & 1) { ret += tlv->value[start / 2] & 0xf; i = start + 1; } else i = start; for (; i < end - 1; i += 2) { ret *= 10; ret += tlv->value[i / 2] >> 4; ret *= 10; ret += tlv->value[i / 2] & 0xf; } if (end & 1) { ret *= 10; ret += tlv->value[end / 2] >> 4; } return ret; } static void piv_tag_dump_yyyymmdd(const struct tlv *tlv, const struct piv_tag *tag, int level) { bool is_printable = true; for (size_t i = 0; i < tlv->len; i++) { if ((tlv->value[i] < 0x30) || (tlv->value[i] > 0x39)) { is_printable = false; break; } } if (is_printable) { PrintAndLogEx(NORMAL, " " _YELLOW_("%c%c%c%c.%c%c.%c%c"), tlv->value[0], tlv->value[1], tlv->value[2], tlv->value[3], tlv->value[4], tlv->value[5], tlv->value[6], tlv->value[7] ); } else { PrintAndLogEx(NORMAL, " " _YELLOW_("%04lu.%02lu.%02lu"), piv_value_numeric(tlv, 0, 4), piv_value_numeric(tlv, 4, 6), piv_value_numeric(tlv, 6, 8) ); } } static void piv_tag_dump_enum(const struct tlv *tlv, const struct piv_tag *tag, int level) { const struct piv_tag_enum *values = tag->data; for (size_t i = 0; values[i].name != NULL; i++) { if (values[i].value == tlv->value[0]) { PrintAndLogEx(NORMAL, " %u - '" _YELLOW_("%s")"'", tlv->value[0], values[i].name); return; } } PrintAndLogEx(NORMAL, " %u - " _RED_("Unknown??"), tlv->value[0]); } static void piv_tag_dump_tlv(const struct tlv *tlv, const struct piv_tag *tag, int level) { // We don't use parsing methods because we need to discard constructed tags const unsigned char *buf = tlv->value; size_t left = tlv->len; while (left) { struct tlv sub_tlv; //const struct piv_tag *sub_tag; if (!tlv_parse_tl(&buf, &left, &sub_tlv)) { PrintAndLogEx(INFO, "%*sInvalid Tag-Len", (level * 4), " "); continue; } sub_tlv.value = buf; piv_tag_dump(&sub_tlv, level + 1); buf += sub_tlv.len; left -= sub_tlv.len; } } static void piv_print_cert(const uint8_t *buf, const size_t len, int level) { char prefix[256] = {0}; PrintAndLogEx(NORMAL, ""); snprintf(prefix, sizeof(prefix), "%*s", 4 * level, " "); // TODO: when mbedTLS has a new release with the PCKS7 parser, we can replace the generic ASN.1 print // The pull request has been merged end of Nov 2022. asn1_print((uint8_t *) buf, len, prefix); } static void piv_print_fascn(const uint8_t *buf, const size_t len, int level) { const char *encoded[32] = { _RED_("?"), // 0b00000 "0", // 0b00001 "8", // 0b00010 _RED_("?"), // 0b00011 "4", // 0b00100 _RED_("?"), // 0b00101 _RED_("?"), // 0b00110 _RED_("?"), // 0b00111 "2", // 0b01000 _RED_("?"), // 0b01001 _RED_("?"), // 0b01010 _RED_("?"), // 0b01011 _RED_("?"), // 0b01100 "6", // 0b01101 _RED_("?"), // 0b01110 _RED_("?"), // 0b01111 "1", // 0b10000 _RED_("?"), // 0b10001 _RED_("?"), // 0b10010 "9", // 0b10011 _RED_("?"), // 0b10100 "5", // 0b10101 _GREEN_(" FS "), // 0b10110 _RED_("?"), // 0b10111 _RED_("?"), // 0b11000 "3", // 0b11001 _YELLOW_("SS "), // 0b11010 _RED_("?"), // 0b11011 "7", // 0b11100 _RED_("?"), // 0b11101 _RED_("?"), // 0b11110 _YELLOW_(" ES"), // 0b11111 }; const uint8_t cycle[8] = {5, 2, 7, 4, 1, 6, 3, 8}; PrintAndLogEx(INFO, "%*s" NOLF, 4 * level, " "); // Buffer is 40 bytes but last byte is LRC that we process separately for (int i = 0; i < 39; i++) { uint8_t tmp = buf[(5 * i) >> 3]; uint8_t rot = cycle[i & 7]; // rotate left to get the bits in place tmp = (tmp << rot) | (tmp >> (8 - rot)); // append bits from next byte if needed if (rot < 5) { uint8_t tmp2 = buf[(5 * (i + 1)) >> 3]; tmp2 = (tmp2 << rot) | (tmp2 >> (8 - rot)); tmp &= 0x1f << rot; tmp |= tmp2 & ((1 << rot) - 1); } PrintAndLogEx(NORMAL, "%s" NOLF, encoded[tmp & 0x1f]); } uint8_t lrc = buf[24] & 0x1f; PrintAndLogEx(NORMAL, " LRC=[" _YELLOW_("%02x") "]", lrc); } static bool piv_tag_dump(const struct tlv *tlv, int level) { if (tlv == NULL) { PrintAndLogEx(FAILED, "NULL"); return false; } const struct piv_tag *tag = piv_get_tag(tlv); PrintAndLogEx(INFO, "%*s--%2x[%02zx] '%s':" NOLF, (level * 4), " ", tlv->tag, tlv->len, tag->name); switch (tag->type) { case PIV_TAG_GENERIC: PrintAndLogEx(NORMAL, ""); break; case PIV_TAG_HEXDUMP: PrintAndLogEx(NORMAL, ""); print_buffer(tlv->value, tlv->len, level + 1); break; case PIV_TAG_STRING: PrintAndLogEx(NORMAL, " '" _YELLOW_("%s")"'", sprint_hex_inrow(tlv->value, tlv->len)); break; case PIV_TAG_NUMERIC: PrintAndLogEx(NORMAL, " " _YELLOW_("%lu"), piv_value_numeric(tlv, 0, tlv->len * 2)); break; case PIV_TAG_YYYYMMDD: piv_tag_dump_yyyymmdd(tlv, tag, level); break; case PIV_TAG_ENUM: piv_tag_dump_enum(tlv, tag, level + 1); break; case PIV_TAG_TLV: PrintAndLogEx(NORMAL, ""); piv_tag_dump_tlv(tlv, tag, level); break; case PIV_TAG_PRINTSTR: PrintAndLogEx(NORMAL, " '" NOLF); for (size_t i = 0; i < tlv->len; i++) { PrintAndLogEx(NORMAL, _YELLOW_("%c") NOLF, tlv->value[i]); } PrintAndLogEx(NORMAL, "'"); break; case PIV_TAG_GUID: if (tlv->len != 16) { PrintAndLogEx(NORMAL, _RED_("")); } else { struct guid guid = {0}; parse_guid(tlv->value, &guid); PrintAndLogEx(NORMAL, " " _YELLOW_("{%08x-%04x-%04x-") NOLF, guid.part1, guid.part2, guid.part3); for (size_t i = 0; i < 8; i++) { PrintAndLogEx(NORMAL, _YELLOW_("%02x") NOLF, guid.data[i]); } PrintAndLogEx(NORMAL, _YELLOW_("}")); } break; case PIV_TAG_CERT: piv_print_cert(tlv->value, tlv->len, level + 2); break; case PIV_TAG_FASCN: PrintAndLogEx(NORMAL, " '" _YELLOW_("%s")"'", sprint_hex_inrow(tlv->value, tlv->len)); if (tlv->len == 25) { piv_print_fascn(tlv->value, tlv->len, level + 2); } break; }; return true; } static void piv_print_cb(void *data, const struct tlv *tlv, int level, bool is_leaf) { piv_tag_dump(tlv, level); if (is_leaf) { print_buffer(tlv->value, tlv->len, level); } } static void PrintTLV(const struct tlvdb *tlvdb) { if (tlvdb) { tlvdb_visit(tlvdb, piv_print_cb, NULL, 0); } } static void PrintTLVFromBuffer(const uint8_t *buf, size_t len) { if (buf == NULL || len == 0) { return; } struct tlvdb_root *root = calloc(1, sizeof(*root) + len); if (root == NULL) { return; } root->len = len; memcpy(root->buf, buf, len); if (tlvdb_parse_root_multi(root) == true) { PrintTLV(&(root->db)); } else { PrintAndLogEx(WARNING, "TLV ERROR: Can't parse buffer as TLV tree."); } tlvdb_root_free(root); } static int PivGetData(Iso7816CommandChannel channel, const uint8_t tag[], size_t tag_len, bool verbose, struct tlvdb_root **result, uint16_t *sw) { uint8_t apdu_data[5] = {0x5c, 0x00}; *result = NULL; *sw = 0; if (tag_len < 1 || tag_len > 3) { return PM3_EINVARG; } apdu_data[1] = tag_len; memcpy(&apdu_data[2], tag, tag_len); sAPDU_t apdu = { .CLA = 0x00, .INS = 0xCB, .P1 = 0x3F, .P2 = 0xFF, .Lc = tag_len + 2, .data = apdu_data }; // Answer can be chained. Let's use a dynamically allocated buffer. size_t capacity = PM3_CMD_DATA_SIZE; struct tlvdb_root *root = calloc(1, sizeof(*root) + capacity); if (root == NULL) { return PM3_EMALLOC; } root->len = 0; size_t more_data = 0; do { size_t received = 0; int res = Iso7816ExchangeEx(channel, false, true, apdu, (more_data != 0), more_data, &(root->buf[root->len]), capacity - root->len, &received, sw); if (res != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Sending APDU failed with code %d", res); free(root); return res; } root->len += received; if (((*sw) & 0xff00) == 0x6100) { // More data more_data = (*sw) & 0xff; if (more_data == 0x00 || more_data > MAX_APDU_SIZE) { more_data = MAX_APDU_SIZE; } apdu.CLA = 0x00; apdu.INS = 0xC0; apdu.P1 = 0x00; apdu.P2 = 0x00; apdu.Lc = 0; apdu.data = NULL; if ((capacity - root->len) < PM3_CMD_DATA_SIZE) { PrintAndLogEx(DEBUG, "Adding more capacity to buffer..."); capacity += PM3_CMD_DATA_SIZE; struct tlvdb_root *new_root = realloc(root, sizeof(*root) + capacity); if (new_root == NULL) { PrintAndLogEx(FAILED, "Running out of memory while re-allocating buffer"); //free(root); tlvdb_root_free(root); return PM3_EMALLOC; } root = new_root; } } if ((*sw) == ISO7816_OK) { more_data = 0; } } while (more_data > 0); // Now we can try parse the TLV and return it *result = root; if (*sw == ISO7816_OK && tlvdb_parse_root(root) == true) { return PM3_SUCCESS; } if (verbose == true) { PrintAndLogEx(WARNING, "Couldn't parse TLV answer."); } return PM3_SUCCESS; } static int PivGetDataByCidAndPrint(Iso7816CommandChannel channel, const struct piv_container *cid, bool decodeTLV, bool verbose) { struct tlvdb_root *root = NULL; if (cid == NULL) { return PM3_SUCCESS; } PrintAndLogEx(INFO, "Getting %s [" _GREEN_("%s") "]", cid->name, sprint_hex_inrow(cid->tlv_tag, cid->len)); uint16_t sw = 0; if (PivGetData(channel, cid->tlv_tag, cid->len, verbose, &root, &sw) == PM3_SUCCESS) { switch (sw) { case ISO7816_OK: if (decodeTLV == true) { PrintTLV(&(root->db)); } else { print_buffer(root->buf, root->len, 0); } break; case ISO7816_FILE_NOT_FOUND: PrintAndLogEx(FAILED, "Container not found."); break; case ISO7816_SECURITY_STATUS_NOT_SATISFIED: PrintAndLogEx(WARNING, "Security conditions not met."); break; default: if (verbose == true) { PrintAndLogEx(INFO, "APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); } break; } tlvdb_root_free(root); } return PM3_SUCCESS; } static int PivGetDataByTagAndPrint(Iso7816CommandChannel channel, const uint8_t tag[], size_t tag_len, bool decodeTLV, bool verbose) { int idx = 0; for (; PIV_CONTAINERS[idx].len != 0; idx++) { if ((tag_len == PIV_CONTAINERS[idx].len) && (memcmp(tag, PIV_CONTAINERS[idx].tlv_tag, tag_len) == 0)) { break; } } if (PIV_CONTAINERS[idx].len == 0) { struct piv_container cid = {0x00, tag, tag_len, PIV_OPTIONAL, "Getting unknown contained ID"}; return PivGetDataByCidAndPrint(channel, &cid, decodeTLV, verbose); } return PivGetDataByCidAndPrint(channel, &(PIV_CONTAINERS[idx]), decodeTLV, verbose); } static int PivAuthenticateSign(Iso7816CommandChannel channel, uint8_t alg_id, uint8_t key_id, uint8_t nonce[], size_t nonce_len, void **result, bool decodeTLV, bool verbose) { const size_t MAX_NONCE_LEN = 0x7a; if (nonce_len > MAX_NONCE_LEN) { if (verbose == true) { PrintAndLogEx(WARNING, "Nonce cannot exceed %zu bytes. Got %zu bytes.", MAX_NONCE_LEN, nonce_len); } return PM3_EINVARG; } uint8_t apdu_buf[APDU_RES_LEN] = {0x7c, nonce_len + 4, 0x82, 0x00, 0x81, nonce_len}; memcpy(&apdu_buf[6], nonce, nonce_len); sAPDU_t apdu = { 0x00, 0x87, alg_id, key_id, 6 + nonce_len, apdu_buf }; uint16_t sw = 0; uint8_t buf[APDU_RES_LEN] = {0}; size_t len = 0; int res = Iso7816ExchangeEx(channel, false, true, apdu, false, 0, buf, APDU_RES_LEN, &len, &sw); if (res != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Sending APDU failed with code %d", res); return res; } if (sw != ISO7816_OK) { if (verbose == true) { PrintAndLogEx(INFO, "Unexpected APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); } return PM3_EFAILED; } if (verbose == true) { if (decodeTLV == true) { PrintTLVFromBuffer(buf, len); } else { print_buffer(buf, len, 0); } } return PM3_SUCCESS; } static int PivSelect(Iso7816CommandChannel channel, bool activateField, bool leaveFieldOn, bool decodeTLV, bool silent, uint8_t applet[], size_t appletLen) { uint8_t buf[APDU_RES_LEN] = {0}; size_t len = 0; uint16_t sw = 0; int res = Iso7816Select(channel, activateField, leaveFieldOn, applet, appletLen, buf, sizeof(buf), &len, &sw); if ((sw != 0) && (silent == false)) { PrintAndLogEx(INFO, "APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); } if (res != PM3_SUCCESS || sw != ISO7816_OK) { PrintAndLogEx(FAILED, "Applet selection failed. Card is not a PIV card."); return res; } if (silent == false) { if (decodeTLV == true) { PrintTLVFromBuffer(buf, len); } else { print_buffer(buf, len, 0); } } return PM3_SUCCESS; } static int CmdPIVSelect(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "piv select", "Executes select applet command", "piv select -s -> select card, select applet\n" "piv select -st --aid a00000030800001000 -> select card, select applet a00000030800001000, show result in TLV\n"); void *argtable[] = { arg_param_begin, arg_lit0("sS", "select", "Activate field and select applet"), arg_lit0("kK", "keep", "Keep field for next command"), arg_lit0("aA", "apdu", "Show APDU requests and responses"), arg_lit0("tT", "tlv", "TLV decode results"), arg_lit0("wW", "wired", "Send data via contact (iso7816) interface. (def: Contactless interface)"), arg_str0(NULL, "aid", "", "Applet ID to select. By default A0000003080000100 will be used"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); bool activateField = arg_get_lit(ctx, 1); bool leaveSignalON = arg_get_lit(ctx, 2); bool APDULogging = arg_get_lit(ctx, 3); bool decodeTLV = arg_get_lit(ctx, 4); Iso7816CommandChannel channel = CC_CONTACTLESS; if (arg_get_lit(ctx, 5)) { channel = CC_CONTACT; } PrintChannel(channel); uint8_t applet_id[APDU_AID_LEN] = {0}; int aid_len = 0; CLIGetHexWithReturn(ctx, 6, applet_id, &aid_len); if (aid_len == 0) { memcpy(applet_id, PIV_APPLET, sizeof(PIV_APPLET)); aid_len = sizeof(PIV_APPLET); } CLIParserFree(ctx); SetAPDULogging(APDULogging); return PivSelect(channel, activateField, leaveSignalON, decodeTLV, false, applet_id, aid_len); } static int CmdPIVGetData(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "piv getdata", "Get a data container of a given tag", "piv getdata -s 5fc102 -> select card, select applet, get card holder unique identifer\n" "piv getdata -st 5fc102 -> select card, select applet, get card holder unique identifer, show result in TLV\n"); void *argtable[] = { arg_param_begin, arg_lit0("sS", "select", "Activate field and select applet"), arg_lit0("kK", "keep", "Keep field for next command"), arg_lit0("aA", "apdu", "Show APDU requests and responses"), arg_lit0("tT", "tlv", "TLV decode results"), arg_lit0("wW", "wired", "Send data via contact (iso7816) interface. (def: Contactless interface)"), arg_str0(NULL, "aid", "", "Applet ID to select. By default A0000003080000100 will be used"), arg_str1(NULL, NULL, "", "Tag ID to read, between 1 and 3 bytes."), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); bool activateField = arg_get_lit(ctx, 1); bool leaveSignalON = arg_get_lit(ctx, 2); bool APDULogging = arg_get_lit(ctx, 3); bool decodeTLV = arg_get_lit(ctx, 4); Iso7816CommandChannel channel = CC_CONTACTLESS; if (arg_get_lit(ctx, 5)) channel = CC_CONTACT; PrintChannel(channel); uint8_t applet_id[APDU_AID_LEN] = {0}; int aid_len = 0; CLIGetHexWithReturn(ctx, 6, applet_id, &aid_len); if (aid_len == 0) { memcpy(applet_id, PIV_APPLET, sizeof(PIV_APPLET)); aid_len = sizeof(PIV_APPLET); } uint8_t tag[4] = {0}; int tag_len = 0; CLIGetHexWithReturn(ctx, 7, tag, &tag_len); CLIParserFree(ctx); if ((tag_len < 1) || (tag_len > 3)) { PrintAndLogEx(WARNING, "Tag should be between 1 and 3 bytes. Got %i", tag_len); return PM3_EINVARG; } SetAPDULogging(APDULogging); int res = 0; if (activateField == true) { res = PivSelect(channel, activateField, true, decodeTLV, true, applet_id, aid_len); if (res != PM3_SUCCESS) { if (leaveSignalON == false) { DropFieldEx(channel); } return res; } } res = PivGetDataByTagAndPrint(channel, tag, tag_len, decodeTLV, false); if (leaveSignalON == false) { DropFieldEx(channel); } return res; } static int CmdPIVAuthenticateSign(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "piv sign", "Send a nonce and ask the PIV card to sign it", "piv sign -sk -> select card, select applet, sign a NULL nonce\n"); void *argtable[] = { arg_param_begin, arg_lit0("sS", "select", "Activate field and select applet"), arg_lit0("kK", "keep", "Keep field for next command"), arg_lit0("aA", "apdu", "Show APDU requests and responses"), arg_lit0("tT", "tlv", "TLV decode results"), arg_lit0("wW", "wired", "Send data via contact (iso7816) interface. (def: Contactless interface)"), arg_str0(NULL, "aid", "", "Applet ID to select. By default A0000003080000100 will be used"), arg_str1(NULL, "nonce", "", "Nonce to sign."), arg_int0(NULL, "slot", "", "Slot number. Default will be 0x9E (card auth cert)."), arg_int0(NULL, "alg", "", "Algorithm to use to sign. Example values: 06=RSA-1024, 07=RSA-2048, 11=ECC-P256 (default), 14=ECC-P384"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); bool activateField = arg_get_lit(ctx, 1); bool leaveSignalON = arg_get_lit(ctx, 2); bool APDULogging = arg_get_lit(ctx, 3); bool decodeTLV = arg_get_lit(ctx, 4); Iso7816CommandChannel channel = CC_CONTACTLESS; if (arg_get_lit(ctx, 5)) channel = CC_CONTACT; PrintChannel(channel); uint8_t applet_id[APDU_AID_LEN] = {0}; int aid_len = 0; CLIGetHexWithReturn(ctx, 6, applet_id, &aid_len); if (aid_len == 0) { memcpy(applet_id, PIV_APPLET, sizeof(PIV_APPLET)); aid_len = sizeof(PIV_APPLET); } uint8_t nonce[APDU_RES_LEN] = {0}; int nonce_len = 0; CLIGetHexWithReturn(ctx, 7, nonce, &nonce_len); int key_slot = arg_get_int_def(ctx, 8, 0x9e); int alg_id = arg_get_int_def(ctx, 9, 0x11); CLIParserFree(ctx); if (key_slot > 0xff) { PrintAndLogEx(FAILED, "Key slot must fit on 1 byte."); return PM3_EINVARG; } if (alg_id > 0xff) { PrintAndLogEx(FAILED, "Algorithm ID must fit on 1 byte"); return PM3_EINVARG; } SetAPDULogging(APDULogging); int res = 0; if (activateField == true) { res = PivSelect(channel, activateField, true, decodeTLV, true, applet_id, aid_len); if (res != PM3_SUCCESS) { if (leaveSignalON == false) { DropFieldEx(channel); } return res; } } res = PivAuthenticateSign(channel, (uint8_t)(alg_id & 0xff), (uint8_t)(key_slot & 0xff), nonce, nonce_len, NULL, decodeTLV, true); if (leaveSignalON == false) { DropFieldEx(channel); } return res; } static int CmdPIVScan(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "piv scan", "Scan a PIV card for known containers", "piv scan -s -> select card, select applet and run scan\n" "piv scan -st --aid a00000030800001000 -> select card, select applet a00000030800001000, show result of the scan in TLV\n"); void *argtable[] = { arg_param_begin, arg_lit0("sS", "select", "Activate field and select applet"), arg_lit0("kK", "keep", "Keep field for next command"), arg_lit0("aA", "apdu", "Show APDU requests and responses"), arg_lit0("tT", "tlv", "TLV decode results"), arg_lit0("wW", "wired", "Send data via contact (iso7816) interface. (def: Contactless interface)"), arg_str0(NULL, "aid", "", "Applet ID to select. By default A0000003080000100 will be used"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); bool activateField = arg_get_lit(ctx, 1); bool leaveSignalON = arg_get_lit(ctx, 2); bool APDULogging = arg_get_lit(ctx, 3); bool decodeTLV = arg_get_lit(ctx, 4); Iso7816CommandChannel channel = CC_CONTACTLESS; if (arg_get_lit(ctx, 5)) channel = CC_CONTACT; PrintChannel(channel); uint8_t applet_id[APDU_AID_LEN] = {0}; int aid_len = 0; CLIGetHexWithReturn(ctx, 6, applet_id, &aid_len); if (aid_len == 0) { memcpy(applet_id, PIV_APPLET, sizeof(PIV_APPLET)); aid_len = sizeof(PIV_APPLET); } CLIParserFree(ctx); SetAPDULogging(APDULogging); if (aid_len == 0) { memcpy(applet_id, PIV_APPLET, sizeof(PIV_APPLET)); aid_len = sizeof(PIV_APPLET); } if (activateField == true) { int res = PivSelect(channel, activateField, true, decodeTLV, true, applet_id, aid_len); if (res != PM3_SUCCESS) { if (leaveSignalON == false) { DropFieldEx(channel); } return res; } } for (int i = 0; PIV_CONTAINERS[i].len != 0; i++) { PivGetDataByCidAndPrint(channel, &(PIV_CONTAINERS[i]), decodeTLV, false); PrintAndLogEx(NORMAL, ""); } if (leaveSignalON == false) { DropFieldEx(channel); } return PM3_SUCCESS; } static int CmdPIVList(const char *Cmd) { return CmdTraceListAlias(Cmd, "piv", "7816"); } static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help"}, {"select", CmdPIVSelect, IfPm3Iso14443, "Select the PIV applet"}, {"getdata", CmdPIVGetData, IfPm3Iso14443, "Gets a container on a PIV card"}, {"authsign", CmdPIVAuthenticateSign, IfPm3Iso14443, "Authenticate with the card"}, {"scan", CmdPIVScan, IfPm3Iso14443, "Scan PIV card for known containers"}, {"list", CmdPIVList, AlwaysAvailable, "List ISO7816 history"}, {NULL, NULL, NULL, NULL} }; static int CmdHelp(const char *Cmd) { (void)Cmd; // Cmd is not used so far CmdsHelp(CommandTable); return PM3_SUCCESS; } int CmdPIV(const char *Cmd) { clearCommandBuffer(); return CmdsParse(CommandTable, Cmd); }