mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-21 05:43:48 -07:00
commit
cef0da95ee
6 changed files with 849 additions and 6 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -3,17 +3,18 @@ All notable changes to this project will be documented in this file.
|
||||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||||
|
|
||||||
## [unreleased][unreleased]
|
## [unreleased][unreleased]
|
||||||
|
- Add support for PIV commands, over wired and contactless interfaces (@jmichelp)
|
||||||
- Add `--shallow` option to `hf iclass` reader commands to do shallow (ASK) reader modulation instead of OOK (@nvx)
|
- Add `--shallow` option to `hf iclass` reader commands to do shallow (ASK) reader modulation instead of OOK (@nvx)
|
||||||
- Improved NXP SLI/SLIX series tag identification (@nvx)
|
- Improved NXP SLI/SLIX series tag identification (@nvx)
|
||||||
- Fixed buffer overflow in "lf em 4x05 sniff" (@HeinrichsH)
|
- Fixed buffer overflow in "lf em 4x05 sniff" (@HeinrichsH)
|
||||||
- Fixed potential NULL array printing (@jmichel)
|
- Fixed potential NULL array printing (@jmichelp)
|
||||||
- Added PIV aid to resource file (@jmichel)
|
- Added PIV aid to resource file (@jmichelp)
|
||||||
- Fixed failing compilation on Proxspace environment due to how python is initialized (@jmichel)
|
- Fixed failing compilation on Proxspace environment due to how python is initialized (@jmichelp)
|
||||||
- Fixed length check in sim module communications (@jmichel)
|
- Fixed length check in sim module communications (@jmichelp)
|
||||||
- Changed timings in i2c.c when communicating with sim module (@iceman1001)
|
- Changed timings in i2c.c when communicating with sim module (@iceman1001)
|
||||||
- Moved to non-deprecated API to initialize Python interpreter (@jmichel)
|
- Moved to non-deprecated API to initialize Python interpreter (@jmichelp)
|
||||||
- Changed `sc upgrade` updated firmware v4.13 (RDV40) - frame buffer is now 384 bytes (@sentiprox)
|
- Changed `sc upgrade` updated firmware v4.13 (RDV40) - frame buffer is now 384 bytes (@sentiprox)
|
||||||
- Fixed contact interface / smartcard APDU chaining logic and allow 256 bytes ADPU payload. Need SIM firmware 4.13 to work (@jmichel)
|
- Fixed contact interface / smartcard APDU chaining logic and allow 256 bytes ADPU payload. Need SIM firmware 4.13 to work (@jmichelp)
|
||||||
- Fixed `lf hitag dump` - Should now work as described in the command help (@natmchugh)
|
- Fixed `lf hitag dump` - Should now work as described in the command help (@natmchugh)
|
||||||
- Fixed SPI flash overflow when loading dictionnaries into flash. Breaking change: added 1 more sector for Mifare - dictionnaries should be loaded again (@jmichelp)
|
- Fixed SPI flash overflow when loading dictionnaries into flash. Breaking change: added 1 more sector for Mifare - dictionnaries should be loaded again (@jmichelp)
|
||||||
- Added `hf mf gload, gsave, ggetblk, gsetblk` for Gen4 GTU in mifare classic mode (@DidierA)
|
- Added `hf mf gload, gsave, ggetblk, gsetblk` for Gen4 GTU in mifare classic mode (@DidierA)
|
||||||
|
|
|
@ -336,6 +336,7 @@ set (TARGET_SOURCES
|
||||||
${PM3_ROOT}/client/src/cmdmain.c
|
${PM3_ROOT}/client/src/cmdmain.c
|
||||||
${PM3_ROOT}/client/src/cmdnfc.c
|
${PM3_ROOT}/client/src/cmdnfc.c
|
||||||
${PM3_ROOT}/client/src/cmdparser.c
|
${PM3_ROOT}/client/src/cmdparser.c
|
||||||
|
${PM3_ROOT}/client/src/cmdpiv.c
|
||||||
${PM3_ROOT}/client/src/cmdscript.c
|
${PM3_ROOT}/client/src/cmdscript.c
|
||||||
${PM3_ROOT}/client/src/cmdsmartcard.c
|
${PM3_ROOT}/client/src/cmdsmartcard.c
|
||||||
${PM3_ROOT}/client/src/cmdtrace.c
|
${PM3_ROOT}/client/src/cmdtrace.c
|
||||||
|
|
|
@ -626,6 +626,7 @@ SRCS = mifare/aiddesfire.c \
|
||||||
cmdmain.c \
|
cmdmain.c \
|
||||||
cmdnfc.c \
|
cmdnfc.c \
|
||||||
cmdparser.c \
|
cmdparser.c \
|
||||||
|
cmdpiv.c \
|
||||||
cmdscript.c \
|
cmdscript.c \
|
||||||
cmdsmartcard.c \
|
cmdsmartcard.c \
|
||||||
cmdtrace.c \
|
cmdtrace.c \
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include "cmdanalyse.h"
|
#include "cmdanalyse.h"
|
||||||
#include "emv/cmdemv.h" // EMV
|
#include "emv/cmdemv.h" // EMV
|
||||||
#include "cmdflashmem.h" // rdv40 flashmem commands
|
#include "cmdflashmem.h" // rdv40 flashmem commands
|
||||||
|
#include "cmdpiv.h"
|
||||||
#include "cmdsmartcard.h" // rdv40 smart card ISO7816 commands
|
#include "cmdsmartcard.h" // rdv40 smart card ISO7816 commands
|
||||||
#include "cmdusart.h" // rdv40 FPC USART commands
|
#include "cmdusart.h" // rdv40 FPC USART commands
|
||||||
#include "cmdwiegand.h" // wiegand commands
|
#include "cmdwiegand.h" // wiegand commands
|
||||||
|
@ -324,6 +325,7 @@ static command_t CommandTable[] = {
|
||||||
{"lf", CmdLF, AlwaysAvailable, "{ Low frequency commands... }"},
|
{"lf", CmdLF, AlwaysAvailable, "{ Low frequency commands... }"},
|
||||||
{"mem", CmdFlashMem, IfPm3Flash, "{ Flash memory manipulation... }"},
|
{"mem", CmdFlashMem, IfPm3Flash, "{ Flash memory manipulation... }"},
|
||||||
{"nfc", CmdNFC, AlwaysAvailable, "{ NFC commands... }"},
|
{"nfc", CmdNFC, AlwaysAvailable, "{ NFC commands... }"},
|
||||||
|
{"piv", CmdPIV, AlwaysAvailable, "{ PIV commands... }"},
|
||||||
{"reveng", CmdRev, AlwaysAvailable, "{ CRC calculations from RevEng software... }"},
|
{"reveng", CmdRev, AlwaysAvailable, "{ CRC calculations from RevEng software... }"},
|
||||||
{"smart", CmdSmartcard, AlwaysAvailable, "{ Smart card ISO-7816 commands... }"},
|
{"smart", CmdSmartcard, AlwaysAvailable, "{ Smart card ISO-7816 commands... }"},
|
||||||
{"script", CmdScript, AlwaysAvailable, "{ Scripting commands... }"},
|
{"script", CmdScript, AlwaysAvailable, "{ Scripting commands... }"},
|
||||||
|
|
812
client/src/cmdpiv.c
Normal file
812
client/src/cmdpiv.c
Normal file
|
@ -0,0 +1,812 @@
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
static int CmdHelp(const char *Cmd);
|
||||||
|
|
||||||
|
static const uint16_t APDU_RES_SUCCESS = 0x9000;
|
||||||
|
static const uint16_t APDU_RES_NOT_FOUND = 0x6A82;
|
||||||
|
static const uint16_t APDU_RES_SECURITY = 0x6982;
|
||||||
|
|
||||||
|
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_("<Invalid>"));
|
||||||
|
} 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);
|
||||||
|
return PM3_EMALLOC;
|
||||||
|
}
|
||||||
|
root = new_root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((*sw) == APDU_RES_SUCCESS) {
|
||||||
|
more_data = 0;
|
||||||
|
}
|
||||||
|
} while (more_data > 0);
|
||||||
|
|
||||||
|
// Now we can try parse the TLV and return it
|
||||||
|
*result = root;
|
||||||
|
if (*sw == APDU_RES_SUCCESS && 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 APDU_RES_SUCCESS:
|
||||||
|
if (decodeTLV == true) {
|
||||||
|
PrintTLV(&(root->db));
|
||||||
|
} else {
|
||||||
|
print_buffer(root->buf, root->len, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APDU_RES_NOT_FOUND:
|
||||||
|
PrintAndLogEx(FAILED, "Container not found.");
|
||||||
|
break;
|
||||||
|
case APDU_RES_SECURITY:
|
||||||
|
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 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 != APDU_RES_SUCCESS) {
|
||||||
|
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", "<hex>", "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", "<hex>", "Applet ID to select. By default A0000003080000100 will be used"),
|
||||||
|
arg_str1(NULL, NULL, "<hex>", "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 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"},
|
||||||
|
|
||||||
|
{"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);
|
||||||
|
}
|
||||||
|
|
26
client/src/cmdpiv.h
Normal file
26
client/src/cmdpiv.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// 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
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifndef CMDPIV_H__
|
||||||
|
#define CMDPIV_H__
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
int CmdPIV(const char *Cmd);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Add table
Add a link
Reference in a new issue