1 //-----------------------------------------------------------------------------
2 // Copyright (C) 2020 A. Ozkal
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
7 //-----------------------------------------------------------------------------
8 // High frequency Electronic Machine Readable Travel Document commands
9 //-----------------------------------------------------------------------------
11 // This code is heavily based on mrpkey.py of RFIDIOt
13 #include "cmdhfemrtd.h"
15 #include "fileutils.h" // saveFile
16 #include "cmdparser.h" // command_t
17 #include "cmdtrace.h" // CmdTraceList
18 #include "cliparser.h" // CLIParserContext etc
19 #include "protocols.h" // definitions of ISO14A/7816 protocol
20 #include "iso7816/apduinfo.h" // GetAPDUCodeDescription
21 #include "iso7816/iso7816core.h" // Iso7816ExchangeEx etc
22 #include "crypto/libpcrypto.h" // Hash calculation (sha1, sha256, sha512)
23 #include "mifare/desfire_crypto.h" // des_encrypt/des_decrypt
24 #include "des.h" // mbedtls_des_key_set_parity
25 #include "crapto1/crapto1.h" // prng_successor
26 #include "commonutil.h" // num_to_bytes
27 #include "util_posix.h" // msclock
29 // Max file size in bytes. Used in several places.
30 // Average EF_DG2 seems to be around 20-25kB or so, but ICAO doesn't set an upper limit
31 // Iris data seems to be suggested to be around 35kB per eye (Presumably bumping up the file size to around 70kB)
32 // but as we cannot read that until we implement PACE, 35k seems to be a safe point.
33 #define EMRTD_MAX_FILE_SIZE 35000
36 #define EMRTD_SELECT 0xA4
37 #define EMRTD_EXTERNAL_AUTHENTICATE 0x82
38 #define EMRTD_GET_CHALLENGE 0x84
39 #define EMRTD_READ_BINARY 0xB0
40 #define EMRTD_P1_SELECT_BY_EF 0x02
41 #define EMRTD_P1_SELECT_BY_NAME 0x04
42 #define EMRTD_P2_PROPRIETARY 0x0C
45 #define EMRTD_AID_MRTD {0xA0, 0x00, 0x00, 0x02, 0x47, 0x10, 0x01}
48 const uint8_t KENC_type
[4] = {0x00, 0x00, 0x00, 0x01};
49 const uint8_t KMAC_type
[4] = {0x00, 0x00, 0x00, 0x02};
51 static int emrtd_dump_ef_dg2(uint8_t *file_contents
, size_t file_length
, const char *path
);
52 static int emrtd_dump_ef_dg5(uint8_t *file_contents
, size_t file_length
, const char *path
);
53 static int emrtd_dump_ef_dg7(uint8_t *file_contents
, size_t file_length
, const char *path
);
54 static int emrtd_dump_ef_sod(uint8_t *file_contents
, size_t file_length
, const char *path
);
55 static int emrtd_print_ef_com_info(uint8_t *data
, size_t datalen
);
56 static int emrtd_print_ef_dg1_info(uint8_t *data
, size_t datalen
);
57 static int emrtd_print_ef_dg11_info(uint8_t *data
, size_t datalen
);
58 static int emrtd_print_ef_dg12_info(uint8_t *data
, size_t datalen
);
59 static int emrtd_print_ef_cardaccess_info(uint8_t *data
, size_t datalen
);
61 typedef enum { // list must match dg_table
84 static emrtd_dg_t dg_table
[] = {
85 // tag dg# fileid filename desc pace eac req fast parser dumper
86 {0x60, 0, 0x011E, "EF_COM", "Header and Data Group Presence Information", false, false, true, true, emrtd_print_ef_com_info
, NULL
},
87 {0x61, 1, 0x0101, "EF_DG1", "Details recorded in MRZ", false, false, true, true, emrtd_print_ef_dg1_info
, NULL
},
88 {0x75, 2, 0x0102, "EF_DG2", "Encoded Face", false, false, true, false, NULL
, emrtd_dump_ef_dg2
},
89 {0x63, 3, 0x0103, "EF_DG3", "Encoded Finger(s)", false, true, false, false, NULL
, NULL
},
90 {0x76, 4, 0x0104, "EF_DG4", "Encoded Eye(s)", false, true, false, false, NULL
, NULL
},
91 {0x65, 5, 0x0105, "EF_DG5", "Displayed Portrait", false, false, false, false, NULL
, emrtd_dump_ef_dg5
},
92 {0x66, 6, 0x0106, "EF_DG6", "Reserved for Future Use", false, false, false, false, NULL
, NULL
},
93 {0x67, 7, 0x0107, "EF_DG7", "Displayed Signature or Usual Mark", false, false, false, false, NULL
, emrtd_dump_ef_dg7
},
94 {0x68, 8, 0x0108, "EF_DG8", "Data Feature(s)", false, false, false, true, NULL
, NULL
},
95 {0x69, 9, 0x0109, "EF_DG9", "Structure Feature(s)", false, false, false, true, NULL
, NULL
},
96 {0x6a, 10, 0x010A, "EF_DG10", "Substance Feature(s)", false, false, false, true, NULL
, NULL
},
97 {0x6b, 11, 0x010B, "EF_DG11", "Additional Personal Detail(s)", false, false, false, true, emrtd_print_ef_dg11_info
, NULL
},
98 {0x6c, 12, 0x010C, "EF_DG12", "Additional Document Detail(s)", false, false, false, true, emrtd_print_ef_dg12_info
, NULL
},
99 {0x6d, 13, 0x010D, "EF_DG13", "Optional Detail(s)", false, false, false, true, NULL
, NULL
},
100 {0x6e, 14, 0x010E, "EF_DG14", "Security Options", false, false, false, true, NULL
, NULL
},
101 {0x6f, 15, 0x010F, "EF_DG15", "Active Authentication Public Key Info", false, false, false, true, NULL
, NULL
},
102 {0x70, 16, 0x0110, "EF_DG16", "Person(s) to Notify", false, false, false, true, NULL
, NULL
},
103 {0x77, 0, 0x011D, "EF_SOD", "Document Security Object", false, false, false, false, NULL
, emrtd_dump_ef_sod
},
104 {0xff, 0, 0x011C, "EF_CardAccess", "PACE SecurityInfos", true, false, true, true, emrtd_print_ef_cardaccess_info
, NULL
},
105 {0xff, 0, 0x011D, "EF_CardSecurity", "PACE SecurityInfos for Chip Authentication Mapping", true, false, false, true, NULL
, NULL
},
106 {0x00, 0, 0, NULL
, NULL
, false, false, false, false, NULL
, NULL
}
109 // https://security.stackexchange.com/questions/131241/where-do-magic-constants-for-signature-algorithms-come-from
110 // https://tools.ietf.org/html/rfc3447#page-43
111 static emrtd_hashalg_t hashalg_table
[] = {
112 // name hash func len len descriptor
113 {"SHA-1", sha1hash
, 20, 7, {0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A}},
114 {"SHA-256", sha256hash
, 32, 11, {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01}},
115 {"SHA-512", sha512hash
, 64, 11, {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03}},
116 {NULL
, NULL
, 0, 0, {}}
119 static emrtd_pacealg_t pacealg_table
[] = {
120 // name keygen descriptor
121 {"DH, Generic Mapping, 3DES-CBC-CBC", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x01, 0x01}},
122 {"DH, Generic Mapping, AES-CMAC-128", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x01, 0x02}},
123 {"DH, Generic Mapping, AES-CMAC-192", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x01, 0x03}},
124 {"DH, Generic Mapping, AES-CMAC-256", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x01, 0x04}},
125 {"ECDH, Generic Mapping, 3DES-CBC-CBC", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x02, 0x01}},
126 {"ECDH, Generic Mapping, AES-CMAC-128", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x02, 0x02}},
127 {"ECDH, Generic Mapping, AES-CMAC-192", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x02, 0x03}},
128 {"ECDH, Generic Mapping, AES-CMAC-256", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x02, 0x04}},
129 {"DH, Integrated Mapping, 3DES-CBC-CBC", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x03, 0x01}},
130 {"DH, Integrated Mapping, AES-CMAC-128", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x03, 0x02}},
131 {"DH, Integrated Mapping, AES-CMAC-192", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x03, 0x03}},
132 {"DH, Integrated Mapping, AES-CMAC-256", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x03, 0x04}},
133 {"ECDH, Integrated Mapping, 3DES-CBC-CBC", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x04, 0x01}},
134 {"ECDH, Integrated Mapping, AES-CMAC-128", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x04, 0x02}},
135 {"ECDH, Integrated Mapping, AES-CMAC-192", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x04, 0x03}},
136 {"ECDH, Integrated Mapping, AES-CMAC-256", NULL
, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x04, 0x04}},
140 static emrtd_pacesdp_t pacesdp_table
[] = {
142 {0, "1024-bit MODP Group with 160-bit Prime Order Subgroup", 1024},
143 {1, "2048-bit MODP Group with 224-bit Prime Order Subgroup", 2048},
144 {2, "2048-bit MODP Group with 256-bit Prime Order Subgroup", 2048},
145 {8, "NIST P-192 (secp192r1)", 192},
146 {10, "NIST P-224 (secp224r1)", 224},
147 {12, "NIST P-256 (secp256r1)", 256},
148 {15, "NIST P-384 (secp384r1)", 384},
149 {18, "NIST P-521 (secp521r1)", 521},
150 {9, "BrainpoolP192r1", 192},
151 {11, "BrainpoolP224r1", 224},
152 {13, "BrainpoolP256r1", 256},
153 {14, "BrainpoolP320r1", 320},
154 {16, "BrainpoolP384r1", 384},
155 {17, "BrainpoolP521r1", 521},
159 static emrtd_dg_t
*emrtd_tag_to_dg(uint8_t tag
) {
160 for (int dgi
= 0; dg_table
[dgi
].filename
!= NULL
; dgi
++) {
161 if (dg_table
[dgi
].tag
== tag
) {
162 return &dg_table
[dgi
];
167 static emrtd_dg_t
*emrtd_fileid_to_dg(uint16_t file_id
) {
168 for (int dgi
= 0; dg_table
[dgi
].filename
!= NULL
; dgi
++) {
169 if (dg_table
[dgi
].fileid
== file_id
) {
170 return &dg_table
[dgi
];
176 static int CmdHelp(const char *Cmd
);
178 static bool emrtd_exchange_commands(sAPDU apdu
, bool include_le
, uint16_t le
, uint8_t *dataout
, size_t maxdataoutlen
, size_t *dataoutlen
, bool activate_field
, bool keep_field_on
) {
180 int res
= Iso7816ExchangeEx(CC_CONTACTLESS
, activate_field
, keep_field_on
, apdu
, include_le
, le
, dataout
, maxdataoutlen
, dataoutlen
, &sw
);
182 if (res
!= PM3_SUCCESS
) {
187 PrintAndLogEx(DEBUG
, "Command failed (%04x - %s).", sw
, GetAPDUCodeDescription(sw
>> 8, sw
& 0xff));
193 static int emrtd_exchange_commands_noout(sAPDU apdu
, bool activate_field
, bool keep_field_on
) {
194 uint8_t response
[PM3_CMD_DATA_SIZE
];
197 return emrtd_exchange_commands(apdu
, false, 0, response
, 0, &resplen
, activate_field
, keep_field_on
);
200 static char emrtd_calculate_check_digit(char *data
) {
201 int mrz_weight
[] = {7, 3, 1};
204 for (int i
= 0; i
< strlen(data
); i
++) {
206 if ('A' <= d
&& d
<= 'Z') {
208 } else if ('a' <= d
&& d
<= 'z') {
210 } else if (d
== '<') {
215 cd
+= value
* mrz_weight
[i
% 3];
220 static int emrtd_get_asn1_data_length(uint8_t *datain
, int datainlen
, int offset
) {
221 PrintAndLogEx(DEBUG
, "asn1 datalength, datain: %s", sprint_hex_inrow(datain
, datainlen
));
222 int lenfield
= (int) * (datain
+ offset
);
223 PrintAndLogEx(DEBUG
, "asn1 datalength, lenfield: %02X", lenfield
);
224 if (lenfield
<= 0x7f) {
226 } else if (lenfield
== 0x80) {
227 // TODO: 0x80 means indeterminate, and this impl is a workaround.
228 // Giving rest of the file is a workaround, nothing more, nothing less.
229 // https://wf.lavatech.top/ave-but-random/emrtd-data-quirks#EF_SOD
231 } else if (lenfield
== 0x81) {
232 int tmp
= (*(datain
+ offset
+ 1));
234 //return ((int) * (datain + offset + 1));
235 } else if (lenfield
== 0x82) {
236 int tmp
= (*(datain
+ offset
+ 1) << 8);
237 tmp
|= *(datain
+ offset
+ 2);
239 //return ((int) * (datain + offset + 1) << 8) | ((int) * (datain + offset + 2));
240 } else if (lenfield
== 0x83) {
241 int tmp
= (*(datain
+ offset
+ 1) << 16);
242 tmp
|= (*(datain
+ offset
+ 2) << 8);
243 tmp
|= *(datain
+ offset
+ 3);
245 //return (((int) * (datain + offset + 1) << 16) | ((int) * (datain + offset + 2)) << 8) | ((int) * (datain + offset + 3));
250 static int emrtd_get_asn1_field_length(uint8_t *datain
, int datainlen
, int offset
) {
251 PrintAndLogEx(DEBUG
, "asn1 fieldlength, datain: %s", sprint_hex_inrow(datain
, datainlen
));
252 int lenfield
= (int) * (datain
+ offset
);
253 PrintAndLogEx(DEBUG
, "asn1 fieldlength, lenfield: %02X", lenfield
);
254 if (lenfield
<= 0x80) {
256 } else if (lenfield
== 0x81) {
258 } else if (lenfield
== 0x82) {
260 } else if (lenfield
== 0x83) {
266 static void des_encrypt_ecb(uint8_t *key
, uint8_t *input
, uint8_t *output
) {
267 mbedtls_des_context ctx_enc
;
268 mbedtls_des_setkey_enc(&ctx_enc
, key
);
269 mbedtls_des_crypt_ecb(&ctx_enc
, input
, output
);
270 mbedtls_des_free(&ctx_enc
);
273 static void des_decrypt_ecb(uint8_t *key
, uint8_t *input
, uint8_t *output
) {
274 mbedtls_des_context ctx_dec
;
275 mbedtls_des_setkey_dec(&ctx_dec
, key
);
276 mbedtls_des_crypt_ecb(&ctx_dec
, input
, output
);
277 mbedtls_des_free(&ctx_dec
);
280 static void des3_encrypt_cbc(uint8_t *iv
, uint8_t *key
, uint8_t *input
, int inputlen
, uint8_t *output
) {
281 mbedtls_des3_context ctx
;
282 mbedtls_des3_set2key_enc(&ctx
, key
);
284 mbedtls_des3_crypt_cbc(&ctx
// des3_context
285 , MBEDTLS_DES_ENCRYPT
// int mode
291 mbedtls_des3_free(&ctx
);
294 static void des3_decrypt_cbc(uint8_t *iv
, uint8_t *key
, uint8_t *input
, int inputlen
, uint8_t *output
) {
295 mbedtls_des3_context ctx
;
296 mbedtls_des3_set2key_dec(&ctx
, key
);
298 mbedtls_des3_crypt_cbc(&ctx
// des3_context
299 , MBEDTLS_DES_DECRYPT
// int mode
305 mbedtls_des3_free(&ctx
);
308 static int pad_block(uint8_t *input
, int inputlen
, uint8_t *output
) {
309 uint8_t padding
[8] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
311 memcpy(output
, input
, inputlen
);
313 int to_pad
= (8 - (inputlen
% 8));
315 for (int i
= 0; i
< to_pad
; i
++) {
316 output
[inputlen
+ i
] = padding
[i
];
319 return inputlen
+ to_pad
;
322 static void retail_mac(uint8_t *key
, uint8_t *input
, int inputlen
, uint8_t *output
) {
323 // This code assumes blocklength (n) = 8, and input len of up to 240 or so chars
324 // This code takes inspirations from https://github.com/devinvenable/iso9797algorithm3
327 uint8_t intermediate
[8] = {0x00};
328 uint8_t intermediate_des
[256];
330 uint8_t message
[256];
334 memcpy(k1
, key
+ 8, 8);
337 int blocksize
= pad_block(input
, inputlen
, message
);
339 // Do chaining and encryption
340 for (int i
= 0; i
< (blocksize
/ 8); i
++) {
341 memcpy(block
, message
+ (i
* 8), 8);
344 for (int x
= 0; x
< 8; x
++) {
345 intermediate
[x
] = intermediate
[x
] ^ block
[x
];
348 des_encrypt_ecb(k0
, intermediate
, intermediate_des
);
349 memcpy(intermediate
, intermediate_des
, 8);
353 des_decrypt_ecb(k1
, intermediate
, intermediate_des
);
354 memcpy(intermediate
, intermediate_des
, 8);
356 des_encrypt_ecb(k0
, intermediate
, intermediate_des
);
357 memcpy(output
, intermediate_des
, 8);
360 static void emrtd_deskey(uint8_t *seed
, const uint8_t *type
, int length
, uint8_t *dataout
) {
361 PrintAndLogEx(DEBUG
, "seed.............. %s", sprint_hex_inrow(seed
, 16));
363 // combine seed and type
365 memcpy(data
, seed
, length
);
366 memcpy(data
+ length
, type
, 4);
367 PrintAndLogEx(DEBUG
, "data.............. %s", sprint_hex_inrow(data
, length
+ 4));
370 unsigned char key
[64];
371 sha1hash(data
, length
+ 4, key
);
372 PrintAndLogEx(DEBUG
, "key............... %s", sprint_hex_inrow(key
, length
+ 4));
375 for (int i
= 0; i
< ((length
+ 4) / 8); i
++) {
376 mbedtls_des_key_set_parity(key
+ (i
* 8));
378 PrintAndLogEx(DEBUG
, "post-parity key... %s", sprint_hex_inrow(key
, 20));
380 memcpy(dataout
, &key
, length
);
383 static void _emrtd_convert_fileid(uint16_t file
, uint8_t *dataout
) {
384 dataout
[0] = file
>> 8;
385 dataout
[1] = file
& 0xFF;
388 static int emrtd_select_file_by_name(uint8_t namelen
, uint8_t *name
) {
389 return emrtd_exchange_commands_noout((sAPDU
) {0, EMRTD_SELECT
, EMRTD_P1_SELECT_BY_NAME
, 0x0C, namelen
, name
}, false, true);
392 static int emrtd_select_file_by_ef(uint16_t file_id
) {
394 _emrtd_convert_fileid(file_id
, data
);
395 return emrtd_exchange_commands_noout((sAPDU
) {0, EMRTD_SELECT
, EMRTD_P1_SELECT_BY_EF
, 0x0C, sizeof(data
), data
}, false, true);
398 static int emrtd_get_challenge(int length
, uint8_t *dataout
, size_t maxdataoutlen
, size_t *dataoutlen
) {
399 return emrtd_exchange_commands((sAPDU
) {0, EMRTD_GET_CHALLENGE
, 0, 0, 0, NULL
}, true, length
, dataout
, maxdataoutlen
, dataoutlen
, false, true);
402 static int emrtd_external_authenticate(uint8_t *data
, int length
, uint8_t *dataout
, size_t maxdataoutlen
, size_t *dataoutlen
) {
403 return emrtd_exchange_commands((sAPDU
) {0, EMRTD_EXTERNAL_AUTHENTICATE
, 0, 0, length
, data
}, true, length
, dataout
, maxdataoutlen
, dataoutlen
, false, true);
406 static int _emrtd_read_binary(int offset
, int bytes_to_read
, uint8_t *dataout
, size_t maxdataoutlen
, size_t *dataoutlen
) {
407 return emrtd_exchange_commands((sAPDU
) {0, EMRTD_READ_BINARY
, offset
>> 8, offset
& 0xFF, 0, NULL
}, true, bytes_to_read
, dataout
, maxdataoutlen
, dataoutlen
, false, true);
410 static void emrtd_bump_ssc(uint8_t *ssc
) {
411 PrintAndLogEx(DEBUG
, "ssc-b: %s", sprint_hex_inrow(ssc
, 8));
412 for (int i
= 7; i
> 0; i
--) {
413 if ((*(ssc
+ i
)) == 0xFF) {
414 // Set anything already FF to 0, we'll do + 1 on num to left anyways
418 PrintAndLogEx(DEBUG
, "ssc-a: %s", sprint_hex_inrow(ssc
, 8));
424 static bool emrtd_check_cc(uint8_t *ssc
, uint8_t *key
, uint8_t *rapdu
, int rapdulength
) {
425 // https://elixi.re/i/clarkson.png
435 if (*(rapdu
) == 0x87) {
436 length
+= 2 + (*(rapdu
+ 1));
437 memcpy(k
+ 8, rapdu
, length
);
438 PrintAndLogEx(DEBUG
, "len1: %i", length
);
441 if ((*(rapdu
+ length
)) == 0x99) {
442 length2
+= 2 + (*(rapdu
+ (length
+ 1)));
443 memcpy(k
+ length
+ 8, rapdu
+ length
, length2
);
444 PrintAndLogEx(DEBUG
, "len2: %i", length2
);
447 int klength
= length
+ length2
+ 8;
449 retail_mac(key
, k
, klength
, cc
);
450 PrintAndLogEx(DEBUG
, "cc: %s", sprint_hex_inrow(cc
, 8));
451 PrintAndLogEx(DEBUG
, "rapdu: %s", sprint_hex_inrow(rapdu
, rapdulength
));
452 PrintAndLogEx(DEBUG
, "rapdu cut: %s", sprint_hex_inrow(rapdu
+ (rapdulength
- 8), 8));
453 PrintAndLogEx(DEBUG
, "k: %s", sprint_hex_inrow(k
, klength
));
455 return memcmp(cc
, rapdu
+ (rapdulength
- 8), 8) == 0;
458 static bool emrtd_secure_select_file_by_ef(uint8_t *kenc
, uint8_t *kmac
, uint8_t *ssc
, uint16_t file
) {
459 uint8_t response
[PM3_CMD_DATA_SIZE
];
462 // convert fileid to bytes
464 _emrtd_convert_fileid(file
, file_id
);
466 uint8_t iv
[8] = { 0x00 };
469 uint8_t temp
[8] = {0x0c, 0xa4, EMRTD_P1_SELECT_BY_EF
, 0x0c};
471 int cmdlen
= pad_block(temp
, 4, cmd
);
472 int datalen
= pad_block(file_id
, 2, data
);
473 PrintAndLogEx(DEBUG
, "cmd: %s", sprint_hex_inrow(cmd
, cmdlen
));
474 PrintAndLogEx(DEBUG
, "data: %s", sprint_hex_inrow(data
, datalen
));
476 des3_encrypt_cbc(iv
, kenc
, data
, datalen
, temp
);
477 PrintAndLogEx(DEBUG
, "temp: %s", sprint_hex_inrow(temp
, datalen
));
478 uint8_t do87
[11] = {0x87, 0x09, 0x01};
479 memcpy(do87
+ 3, temp
, datalen
);
480 PrintAndLogEx(DEBUG
, "do87: %s", sprint_hex_inrow(do87
, datalen
+ 3));
483 memcpy(m
, cmd
, cmdlen
);
484 memcpy(m
+ cmdlen
, do87
, (datalen
+ 3));
485 PrintAndLogEx(DEBUG
, "m: %s", sprint_hex_inrow(m
, datalen
+ cmdlen
+ 3));
491 memcpy(n
+ 8, m
, (cmdlen
+ datalen
+ 3));
492 PrintAndLogEx(DEBUG
, "n: %s", sprint_hex_inrow(n
, (cmdlen
+ datalen
+ 11)));
495 retail_mac(kmac
, n
, (cmdlen
+ datalen
+ 11), cc
);
496 PrintAndLogEx(DEBUG
, "cc: %s", sprint_hex_inrow(cc
, 8));
498 uint8_t do8e
[10] = {0x8E, 0x08};
499 memcpy(do8e
+ 2, cc
, 8);
500 PrintAndLogEx(DEBUG
, "do8e: %s", sprint_hex_inrow(do8e
, 10));
502 int lc
= datalen
+ 3 + 10;
503 PrintAndLogEx(DEBUG
, "lc: %i", lc
);
505 memcpy(data
, do87
, datalen
+ 3);
506 memcpy(data
+ (datalen
+ 3), do8e
, 10);
507 PrintAndLogEx(DEBUG
, "data: %s", sprint_hex_inrow(data
, lc
));
509 if (emrtd_exchange_commands((sAPDU
) {0x0C, EMRTD_SELECT
, EMRTD_P1_SELECT_BY_EF
, 0x0C, lc
, data
}, true, 0, response
, sizeof(response
), &resplen
, false, true) == false) {
513 return emrtd_check_cc(ssc
, kmac
, response
, resplen
);
516 static bool _emrtd_secure_read_binary(uint8_t *kmac
, uint8_t *ssc
, int offset
, int bytes_to_read
, uint8_t *dataout
, size_t maxdataoutlen
, size_t *dataoutlen
) {
519 uint8_t temp
[8] = {0x0c, 0xb0};
521 PrintAndLogEx(DEBUG
, "kmac: %s", sprint_hex_inrow(kmac
, 20));
524 temp
[2] = (uint8_t)(offset
>> 8);
525 temp
[3] = (uint8_t)(offset
>> 0);
527 int cmdlen
= pad_block(temp
, 4, cmd
);
528 PrintAndLogEx(DEBUG
, "cmd: %s", sprint_hex_inrow(cmd
, cmdlen
));
530 uint8_t do97
[3] = {0x97, 0x01, bytes_to_read
};
534 memcpy(m
+ 8, do97
, 3);
540 memcpy(n
+ 8, m
, 11);
541 PrintAndLogEx(DEBUG
, "n: %s", sprint_hex_inrow(n
, 19));
544 retail_mac(kmac
, n
, 19, cc
);
545 PrintAndLogEx(DEBUG
, "cc: %s", sprint_hex_inrow(cc
, 8));
547 uint8_t do8e
[10] = {0x8E, 0x08};
548 memcpy(do8e
+ 2, cc
, 8);
549 PrintAndLogEx(DEBUG
, "do8e: %s", sprint_hex_inrow(do8e
, 10));
552 PrintAndLogEx(DEBUG
, "lc: %i", lc
);
554 memcpy(data
, do97
, 3);
555 memcpy(data
+ 3, do8e
, 10);
556 PrintAndLogEx(DEBUG
, "data: %s", sprint_hex_inrow(data
, lc
));
558 if (emrtd_exchange_commands((sAPDU
) {0x0C, EMRTD_READ_BINARY
, offset
>> 8, offset
& 0xFF, lc
, data
}, true, 0, dataout
, maxdataoutlen
, dataoutlen
, false, true) == false) {
562 return emrtd_check_cc(ssc
, kmac
, dataout
, *dataoutlen
);
565 static bool _emrtd_secure_read_binary_decrypt(uint8_t *kenc
, uint8_t *kmac
, uint8_t *ssc
, int offset
, int bytes_to_read
, uint8_t *dataout
, size_t *dataoutlen
) {
566 uint8_t response
[500];
568 size_t resplen
, cutat
= 0;
569 uint8_t iv
[8] = { 0x00 };
571 if (_emrtd_secure_read_binary(kmac
, ssc
, offset
, bytes_to_read
, response
, sizeof(response
), &resplen
) == false) {
575 PrintAndLogEx(DEBUG
, "secreadbindec, offset %i on read %i: encrypted: %s", offset
, bytes_to_read
, sprint_hex_inrow(response
, resplen
));
577 cutat
= ((int) response
[1]) - 1;
579 des3_decrypt_cbc(iv
, kenc
, response
+ 3, cutat
, temp
);
580 memcpy(dataout
, temp
, bytes_to_read
);
581 PrintAndLogEx(DEBUG
, "secreadbindec, offset %i on read %i: decrypted: %s", offset
, bytes_to_read
, sprint_hex_inrow(temp
, cutat
));
582 PrintAndLogEx(DEBUG
, "secreadbindec, offset %i on read %i: decrypted and cut: %s", offset
, bytes_to_read
, sprint_hex_inrow(dataout
, bytes_to_read
));
583 *dataoutlen
= bytes_to_read
;
587 static int emrtd_read_file(uint8_t *dataout
, size_t *dataoutlen
, uint8_t *kenc
, uint8_t *kmac
, uint8_t *ssc
, bool use_secure
) {
588 uint8_t response
[EMRTD_MAX_FILE_SIZE
];
590 uint8_t tempresponse
[500];
591 size_t tempresplen
= 0;
596 if (_emrtd_secure_read_binary_decrypt(kenc
, kmac
, ssc
, offset
, toread
, response
, &resplen
) == false) {
600 if (_emrtd_read_binary(offset
, toread
, response
, sizeof(response
), &resplen
) == false) {
605 int datalen
= emrtd_get_asn1_data_length(response
, resplen
, 1);
606 int readlen
= datalen
- (3 - emrtd_get_asn1_field_length(response
, resplen
, 1));
609 uint8_t lnbreak
= 32;
610 PrintAndLogEx(INFO
, "." NOLF
);
611 while (readlen
> 0) {
618 if (_emrtd_secure_read_binary_decrypt(kenc
, kmac
, ssc
, offset
, toread
, tempresponse
, &tempresplen
) == false) {
619 PrintAndLogEx(NORMAL
, "");
623 if (_emrtd_read_binary(offset
, toread
, tempresponse
, sizeof(tempresponse
), &tempresplen
) == false) {
624 PrintAndLogEx(NORMAL
, "");
629 memcpy(response
+ resplen
, tempresponse
, tempresplen
);
632 resplen
+= tempresplen
;
634 PrintAndLogEx(NORMAL
, "." NOLF
);
638 PrintAndLogEx(NORMAL
, "");
639 PrintAndLogEx(INFO
, "." NOLF
);
643 PrintAndLogEx(NORMAL
, "");
645 memcpy(dataout
, &response
, resplen
);
646 *dataoutlen
= resplen
;
650 static int emrtd_lds_determine_tag_length(uint8_t tag
) {
651 if ((tag
== 0x5F) || (tag
== 0x7F)) {
657 static bool emrtd_lds_get_data_by_tag(uint8_t *datain
, size_t datainlen
, uint8_t *dataout
, size_t *dataoutlen
, int tag1
, int tag2
, bool twobytetag
, bool entertoptag
, size_t skiptagcount
) {
662 offset
+= emrtd_lds_determine_tag_length(*datain
);
663 offset
+= emrtd_get_asn1_field_length(datain
, datainlen
, offset
);
666 while (offset
< datainlen
) {
667 PrintAndLogEx(DEBUG
, "emrtd_lds_get_data_by_tag, offset: %i, data: %X", offset
, *(datain
+ offset
));
668 // Determine element ID length to set as offset on asn1datalength
669 int e_idlen
= emrtd_lds_determine_tag_length(*(datain
+ offset
));
671 // Get the length of the element
672 int e_datalen
= emrtd_get_asn1_data_length(datain
+ offset
, datainlen
- offset
, e_idlen
);
674 // Get the length of the element's length
675 int e_fieldlen
= emrtd_get_asn1_field_length(datain
+ offset
, datainlen
- offset
, e_idlen
);
677 PrintAndLogEx(DEBUG
, "emrtd_lds_get_data_by_tag, e_idlen: %02X, e_datalen: %02X, e_fieldlen: %02X", e_idlen
, e_datalen
, e_fieldlen
);
679 // If the element is what we're looking for, get the data and return true
680 if (*(datain
+ offset
) == tag1
&& (!twobytetag
|| *(datain
+ offset
+ 1) == tag2
)) {
681 if (skipcounter
< skiptagcount
) {
683 } else if (datainlen
> e_datalen
) {
684 *dataoutlen
= e_datalen
;
685 memcpy(dataout
, datain
+ offset
+ e_idlen
+ e_fieldlen
, e_datalen
);
688 PrintAndLogEx(ERR
, "error (emrtd_lds_get_data_by_tag) e_datalen out-of-bounds");
692 offset
+= e_idlen
+ e_datalen
+ e_fieldlen
;
694 // Return false if we can't find the relevant element
698 static bool emrtd_select_and_read(uint8_t *dataout
, size_t *dataoutlen
, uint16_t file
, uint8_t *ks_enc
, uint8_t *ks_mac
, uint8_t *ssc
, bool use_secure
) {
700 if (emrtd_secure_select_file_by_ef(ks_enc
, ks_mac
, ssc
, file
) == false) {
701 PrintAndLogEx(ERR
, "Failed to secure select %04X", file
);
705 if (emrtd_select_file_by_ef(file
) == false) {
706 PrintAndLogEx(ERR
, "Failed to select %04X", file
);
711 if (emrtd_read_file(dataout
, dataoutlen
, ks_enc
, ks_mac
, ssc
, use_secure
) == false) {
712 PrintAndLogEx(ERR
, "Failed to read %04X", file
);
718 const uint8_t jpeg_header
[4] = { 0xFF, 0xD8, 0xFF, 0xE0 };
719 const uint8_t jpeg2k_header
[6] = { 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50 };
721 static int emrtd_dump_ef_dg2(uint8_t *file_contents
, size_t file_length
, const char *path
) {
722 int offset
, datalen
= 0;
724 // This is a hacky impl that just looks for the image header. I'll improve it eventually.
725 // based on mrpkey.py
726 // Note: Doing file_length - 6 to account for the longest data we're checking.
727 // Checks first byte before the rest to reduce overhead
728 for (offset
= 0; offset
< file_length
- 6; offset
++) {
729 if ((file_contents
[offset
] == 0xFF && memcmp(jpeg_header
, file_contents
+ offset
, 4) == 0) ||
730 (file_contents
[offset
] == 0x00 && memcmp(jpeg2k_header
, file_contents
+ offset
, 6) == 0)) {
731 datalen
= file_length
- offset
;
736 // If we didn't get any data, return false.
741 char *filepath
= calloc(strlen(path
) + 100, sizeof(char));
742 if (filepath
== NULL
)
744 strcpy(filepath
, path
);
745 strncat(filepath
, PATHSEP
, 2);
746 strcat(filepath
, dg_table
[EF_DG2
].filename
);
748 saveFile(filepath
, file_contents
[offset
] == 0xFF ? ".jpg" : ".jp2", file_contents
+ offset
, datalen
);
754 static int emrtd_dump_ef_dg5(uint8_t *file_contents
, size_t file_length
, const char *path
) {
755 uint8_t data
[EMRTD_MAX_FILE_SIZE
];
758 // If we can't find image in EF_DG5, return false.
759 if (emrtd_lds_get_data_by_tag(file_contents
, file_length
, data
, &datalen
, 0x5F, 0x40, true, true, 0) == false) {
763 if (datalen
< EMRTD_MAX_FILE_SIZE
) {
764 char *filepath
= calloc(strlen(path
) + 100, sizeof(char));
765 if (filepath
== NULL
)
767 strcpy(filepath
, path
);
768 strncat(filepath
, PATHSEP
, 2);
769 strcat(filepath
, dg_table
[EF_DG5
].filename
);
771 saveFile(filepath
, data
[0] == 0xFF ? ".jpg" : ".jp2", data
, datalen
);
775 PrintAndLogEx(ERR
, "error (emrtd_dump_ef_dg5) datalen out-of-bounds");
781 static int emrtd_dump_ef_dg7(uint8_t *file_contents
, size_t file_length
, const char *path
) {
782 uint8_t data
[EMRTD_MAX_FILE_SIZE
];
785 // If we can't find image in EF_DG7, return false.
786 if (emrtd_lds_get_data_by_tag(file_contents
, file_length
, data
, &datalen
, 0x5F, 0x42, true, true, 0) == false) {
790 if (datalen
< EMRTD_MAX_FILE_SIZE
) {
791 char *filepath
= calloc(strlen(path
) + 100, sizeof(char));
792 if (filepath
== NULL
)
794 strcpy(filepath
, path
);
795 strncat(filepath
, PATHSEP
, 2);
796 strcat(filepath
, dg_table
[EF_DG7
].filename
);
798 saveFile(filepath
, data
[0] == 0xFF ? ".jpg" : ".jp2", data
, datalen
);
802 PrintAndLogEx(ERR
, "error (emrtd_dump_ef_dg7) datalen out-of-bounds");
808 static int emrtd_dump_ef_sod(uint8_t *file_contents
, size_t file_length
, const char *path
) {
809 int fieldlen
= emrtd_get_asn1_field_length(file_contents
, file_length
, 1);
810 int datalen
= emrtd_get_asn1_data_length(file_contents
, file_length
, 1);
812 if (fieldlen
+ 1 > EMRTD_MAX_FILE_SIZE
) {
813 PrintAndLogEx(ERR
, "error (emrtd_dump_ef_sod) fieldlen out-of-bounds");
814 return PM3_EOUTOFBOUND
;
817 char *filepath
= calloc(strlen(path
) + 100, sizeof(char));
818 if (filepath
== NULL
)
821 strcpy(filepath
, path
);
822 strncat(filepath
, PATHSEP
, 2);
823 strcat(filepath
, dg_table
[EF_SOD
].filename
);
825 saveFile(filepath
, ".p7b", file_contents
+ fieldlen
+ 1, datalen
);
830 static bool emrtd_dump_file(uint8_t *ks_enc
, uint8_t *ks_mac
, uint8_t *ssc
, uint16_t file
, const char *name
, bool use_secure
, const char *path
) {
831 uint8_t response
[EMRTD_MAX_FILE_SIZE
];
834 if (emrtd_select_and_read(response
, &resplen
, file
, ks_enc
, ks_mac
, ssc
, use_secure
) == false) {
838 char *filepath
= calloc(strlen(path
) + 100, sizeof(char));
839 if (filepath
== NULL
)
842 strcpy(filepath
, path
);
843 strncat(filepath
, PATHSEP
, 2);
844 strcat(filepath
, name
);
846 PrintAndLogEx(INFO
, "Read " _YELLOW_("%s") " , len %zu", name
, resplen
);
847 PrintAndLogEx(DEBUG
, "Contents (may be incomplete over 2k chars)");
848 PrintAndLogEx(DEBUG
, "------------------------------------------");
849 PrintAndLogEx(DEBUG
, "%s", sprint_hex_inrow(response
, resplen
));
850 PrintAndLogEx(DEBUG
, "------------------------------------------");
851 saveFile(filepath
, ".BIN", response
, resplen
);
853 emrtd_dg_t
*dg
= emrtd_fileid_to_dg(file
);
854 if ((dg
!= NULL
) && (dg
->dumper
!= NULL
)) {
855 dg
->dumper(response
, resplen
, path
);
862 static void rng(int length
, uint8_t *dataout
) {
863 // Do very very secure prng operations
864 //for (int i = 0; i < (length / 4); i++) {
865 // num_to_bytes(prng_successor(msclock() + i, 32), 4, &dataout[i * 4]);
867 memset(dataout
, 0x00, length
);
870 static bool emrtd_do_bac(char *documentnumber
, char *dob
, char *expiry
, uint8_t *ssc
, uint8_t *ks_enc
, uint8_t *ks_mac
) {
871 uint8_t response
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
874 uint8_t rnd_ic
[10] = { 0x00 }; // 8 + SW
875 uint8_t kenc
[50] = { 0x00 };
876 uint8_t kmac
[50] = { 0x00 };
877 uint8_t k_icc
[16] = { 0x00 };
878 uint8_t S
[32] = { 0x00 };
880 uint8_t rnd_ifd
[8], k_ifd
[16];
884 PrintAndLogEx(DEBUG
, "doc............... " _GREEN_("%s"), documentnumber
);
885 PrintAndLogEx(DEBUG
, "dob............... " _GREEN_("%s"), dob
);
886 PrintAndLogEx(DEBUG
, "exp............... " _GREEN_("%s"), expiry
);
888 char documentnumbercd
= emrtd_calculate_check_digit(documentnumber
);
889 char dobcd
= emrtd_calculate_check_digit(dob
);
890 char expirycd
= emrtd_calculate_check_digit(expiry
);
893 sprintf(kmrz
, "%s%i%s%i%s%i", documentnumber
, documentnumbercd
, dob
, dobcd
, expiry
, expirycd
);
894 PrintAndLogEx(DEBUG
, "kmrz.............. " _GREEN_("%s"), kmrz
);
896 uint8_t kseed
[20] = { 0x00 };
897 sha1hash((unsigned char *)kmrz
, strlen(kmrz
), kseed
);
898 PrintAndLogEx(DEBUG
, "kseed (sha1)...... %s ", sprint_hex_inrow(kseed
, 16));
900 emrtd_deskey(kseed
, KENC_type
, 16, kenc
);
901 emrtd_deskey(kseed
, KMAC_type
, 16, kmac
);
902 PrintAndLogEx(DEBUG
, "kenc.............. %s", sprint_hex_inrow(kenc
, 16));
903 PrintAndLogEx(DEBUG
, "kmac.............. %s", sprint_hex_inrow(kmac
, 16));
906 if (emrtd_get_challenge(8, rnd_ic
, sizeof(rnd_ic
), &resplen
) == false) {
907 PrintAndLogEx(ERR
, "Couldn't get challenge.");
910 PrintAndLogEx(DEBUG
, "rnd_ic............ %s", sprint_hex_inrow(rnd_ic
, 8));
912 memcpy(S
, rnd_ifd
, 8);
913 memcpy(S
+ 8, rnd_ic
, 8);
914 memcpy(S
+ 16, k_ifd
, 16);
916 PrintAndLogEx(DEBUG
, "S................. %s", sprint_hex_inrow(S
, 32));
918 uint8_t iv
[8] = { 0x00 };
919 uint8_t e_ifd
[32] = { 0x00 };
921 des3_encrypt_cbc(iv
, kenc
, S
, sizeof(S
), e_ifd
);
922 PrintAndLogEx(DEBUG
, "e_ifd............. %s", sprint_hex_inrow(e_ifd
, 32));
924 uint8_t m_ifd
[8] = { 0x00 };
926 retail_mac(kmac
, e_ifd
, 32, m_ifd
);
927 PrintAndLogEx(DEBUG
, "m_ifd............. %s", sprint_hex_inrow(m_ifd
, 8));
929 uint8_t cmd_data
[40];
930 memcpy(cmd_data
, e_ifd
, 32);
931 memcpy(cmd_data
+ 32, m_ifd
, 8);
933 // Do external authentication
934 if (emrtd_external_authenticate(cmd_data
, sizeof(cmd_data
), response
, sizeof(response
), &resplen
) == false) {
935 PrintAndLogEx(ERR
, "Couldn't do external authentication. Did you supply the correct MRZ info?");
938 PrintAndLogEx(INFO
, "External authentication with BAC successful.");
940 uint8_t dec_output
[32] = { 0x00 };
941 des3_decrypt_cbc(iv
, kenc
, response
, 32, dec_output
);
942 PrintAndLogEx(DEBUG
, "dec_output........ %s", sprint_hex_inrow(dec_output
, 32));
944 if (memcmp(rnd_ifd
, dec_output
+ 8, 8) != 0) {
945 PrintAndLogEx(ERR
, "Challenge failed, rnd_ifd does not match.");
949 memcpy(k_icc
, dec_output
+ 16, 16);
951 // Calculate session keys
952 for (int x
= 0; x
< 16; x
++) {
953 kseed
[x
] = k_ifd
[x
] ^ k_icc
[x
];
956 PrintAndLogEx(DEBUG
, "kseed............ %s", sprint_hex_inrow(kseed
, 16));
958 emrtd_deskey(kseed
, KENC_type
, 16, ks_enc
);
959 emrtd_deskey(kseed
, KMAC_type
, 16, ks_mac
);
961 PrintAndLogEx(DEBUG
, "ks_enc........ %s", sprint_hex_inrow(ks_enc
, 16));
962 PrintAndLogEx(DEBUG
, "ks_mac........ %s", sprint_hex_inrow(ks_mac
, 16));
964 memcpy(ssc
, rnd_ic
+ 4, 4);
965 memcpy(ssc
+ 4, rnd_ifd
+ 4, 4);
967 PrintAndLogEx(DEBUG
, "ssc........... %s", sprint_hex_inrow(ssc
, 8));
972 static bool emrtd_connect(void) {
973 int res
= Iso7816Connect(CC_CONTACTLESS
);
974 return res
== PM3_SUCCESS
;
977 static bool emrtd_do_auth(char *documentnumber
, char *dob
, char *expiry
, bool BAC_available
, bool *BAC
, uint8_t *ssc
, uint8_t *ks_enc
, uint8_t *ks_mac
) {
979 // Select MRTD applet
980 uint8_t aid
[] = EMRTD_AID_MRTD
;
981 if (emrtd_select_file_by_name(sizeof(aid
), aid
) == false) {
982 PrintAndLogEx(ERR
, "Couldn't select the MRTD application.");
987 if (emrtd_select_file_by_ef(dg_table
[EF_COM
].fileid
) == false) {
989 PrintAndLogEx(INFO
, "Authentication is enforced. Will attempt external authentication.");
993 emrtd_select_file_by_ef(dg_table
[EF_DG1
].fileid
);
996 uint8_t response
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
997 if (emrtd_read_file(response
, &resplen
, NULL
, NULL
, NULL
, false) == false) {
999 PrintAndLogEx(INFO
, "Authentication is enforced. Will attempt external authentication.");
1005 // Do Basic Access Control
1007 // If BAC isn't available, exit out and warn user.
1008 if (!BAC_available
) {
1009 PrintAndLogEx(ERR
, "This eMRTD enforces authentication, but you didn't supply MRZ data. Cannot proceed.");
1010 PrintAndLogEx(HINT
, "Check out hf emrtd info/dump --help, supply data with -n -d and -e.");
1014 if (emrtd_do_bac(documentnumber
, dob
, expiry
, ssc
, ks_enc
, ks_mac
) == false) {
1021 int dumpHF_EMRTD(char *documentnumber
, char *dob
, char *expiry
, bool BAC_available
, const char *path
) {
1022 uint8_t response
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
1024 uint8_t ssc
[8] = { 0x00 };
1025 uint8_t ks_enc
[16] = { 0x00 };
1026 uint8_t ks_mac
[16] = { 0x00 };
1030 if (emrtd_connect() == false) {
1035 // Dump EF_CardAccess (if available)
1036 if (!emrtd_dump_file(ks_enc
, ks_mac
, ssc
, dg_table
[EF_CardAccess
].fileid
, dg_table
[EF_CardAccess
].filename
, BAC
, path
)) {
1037 PrintAndLogEx(INFO
, "Couldn't dump EF_CardAccess, card does not support PACE");
1038 PrintAndLogEx(HINT
, "This is expected behavior for cards without PACE, and isn't something to be worried about");
1041 // Authenticate with the eMRTD
1042 if (!emrtd_do_auth(documentnumber
, dob
, expiry
, BAC_available
, &BAC
, ssc
, ks_enc
, ks_mac
)) {
1048 if (!emrtd_select_and_read(response
, &resplen
, dg_table
[EF_COM
].fileid
, ks_enc
, ks_mac
, ssc
, BAC
)) {
1049 PrintAndLogEx(ERR
, "Failed to read EF_COM");
1055 char *filepath
= calloc(strlen(path
) + 100, sizeof(char));
1056 if (filepath
== NULL
)
1059 strcpy(filepath
, path
);
1060 strncat(filepath
, PATHSEP
, 2);
1061 strcat(filepath
, dg_table
[EF_COM
].filename
);
1063 PrintAndLogEx(INFO
, "Read EF_COM, len: %zu", resplen
);
1064 PrintAndLogEx(DEBUG
, "Contents (may be incomplete over 2k chars): %s", sprint_hex_inrow(response
, resplen
));
1065 saveFile(filepath
, ".BIN", response
, resplen
);
1069 uint8_t filelist
[50];
1070 size_t filelistlen
= 0;
1072 if (emrtd_lds_get_data_by_tag(response
, resplen
, filelist
, &filelistlen
, 0x5c, 0x00, false, true, 0) == false) {
1073 PrintAndLogEx(ERR
, "Failed to read file list from EF_COM");
1078 PrintAndLogEx(DEBUG
, "File List: %s", sprint_hex_inrow(filelist
, filelistlen
));
1079 // Add EF_SOD to the list
1080 filelist
[filelistlen
++] = 0x77;
1081 // Dump all files in the file list
1082 for (int i
= 0; i
< filelistlen
; i
++) {
1083 emrtd_dg_t
*dg
= emrtd_tag_to_dg(filelist
[i
]);
1085 PrintAndLogEx(INFO
, "File tag not found, skipping: %02X", filelist
[i
]);
1088 PrintAndLogEx(DEBUG
, "Current file: %s", dg
->filename
);
1089 if (!dg
->pace
&& !dg
->eac
) {
1090 emrtd_dump_file(ks_enc
, ks_mac
, ssc
, dg
->fileid
, dg
->filename
, BAC
, path
);
1097 static bool emrtd_compare_check_digit(char *datain
, int datalen
, char expected_check_digit
) {
1098 char tempdata
[90] = { 0x00 };
1099 memcpy(tempdata
, datain
, datalen
);
1101 uint8_t check_digit
= emrtd_calculate_check_digit(tempdata
) + 0x30;
1102 bool res
= check_digit
== expected_check_digit
;
1103 PrintAndLogEx(DEBUG
, "emrtd_compare_check_digit, expected %c == %c calculated ( %s )"
1104 , expected_check_digit
1106 , (res
) ? _GREEN_("ok") : _RED_("fail"));
1110 static bool emrtd_mrz_verify_check_digit(char *mrz
, int offset
, int datalen
) {
1111 char tempdata
[90] = { 0x00 };
1112 memcpy(tempdata
, mrz
+ offset
, datalen
);
1113 return emrtd_compare_check_digit(tempdata
, datalen
, mrz
[offset
+ datalen
]);
1116 static void emrtd_print_legal_sex(char *legal_sex
) {
1117 char sex
[12] = { 0x00 };
1118 switch (*legal_sex
) {
1120 strncpy(sex
, "Male", 5);
1123 strncpy(sex
, "Female", 7);
1126 strncpy(sex
, "Unspecified", 12);
1129 PrintAndLogEx(SUCCESS
, "Legal Sex Marker......: " _YELLOW_("%s"), sex
);
1132 static int emrtd_mrz_determine_length(char *mrz
, int offset
, int max_length
) {
1134 for (i
= max_length
; i
>= 1; i
--) {
1135 if (mrz
[offset
+ i
- 1] != '<') {
1143 static int emrtd_mrz_determine_separator(char *mrz
, int offset
, int max_length
) {
1144 // Note: this function does not account for len=0
1146 for (i
= max_length
- 1; i
> 0; i
--) {
1147 if (mrz
[offset
+ i
] == '<' && mrz
[offset
+ i
+ 1] == '<') {
1154 static void emrtd_mrz_replace_pad(char *data
, int datalen
, char newchar
) {
1155 for (int i
= 0; i
< datalen
; i
++) {
1156 if (data
[i
] == '<') {
1162 static void emrtd_print_optional_elements(char *mrz
, int offset
, int length
, bool verify_check_digit
) {
1163 int i
= emrtd_mrz_determine_length(mrz
, offset
, length
);
1168 PrintAndLogEx(SUCCESS
, "Optional elements.....: " _YELLOW_("%.*s"), i
, mrz
+ offset
);
1170 if (verify_check_digit
&& !emrtd_mrz_verify_check_digit(mrz
, offset
, length
)) {
1171 PrintAndLogEx(SUCCESS
, _RED_("Optional element check digit is invalid."));
1175 static void emrtd_print_document_number(char *mrz
, int offset
) {
1176 int i
= emrtd_mrz_determine_length(mrz
, offset
, 9);
1181 PrintAndLogEx(SUCCESS
, "Document Number.......: " _YELLOW_("%.*s"), i
, mrz
+ offset
);
1183 if (!emrtd_mrz_verify_check_digit(mrz
, offset
, 9)) {
1184 PrintAndLogEx(SUCCESS
, _RED_("Document number check digit is invalid."));
1188 static void emrtd_print_name(char *mrz
, int offset
, int max_length
, bool localized
) {
1189 char final_name
[100] = { 0x00 };
1190 int namelen
= emrtd_mrz_determine_length(mrz
, offset
, max_length
);
1194 int sep
= emrtd_mrz_determine_separator(mrz
, offset
, namelen
);
1196 // Account for mononyms
1198 int firstnamelen
= (namelen
- (sep
+ 2));
1200 memcpy(final_name
, mrz
+ offset
+ sep
+ 2, firstnamelen
);
1201 final_name
[firstnamelen
] = ' ';
1202 memcpy(final_name
+ firstnamelen
+ 1, mrz
+ offset
, sep
);
1204 memcpy(final_name
, mrz
+ offset
, namelen
);
1207 // Replace < characters with spaces
1208 emrtd_mrz_replace_pad(final_name
, namelen
, ' ');
1211 PrintAndLogEx(SUCCESS
, "Legal Name (Localized): " _YELLOW_("%s"), final_name
);
1213 PrintAndLogEx(SUCCESS
, "Legal Name............: " _YELLOW_("%s"), final_name
);
1217 static void emrtd_mrz_convert_date(char *mrz
, int offset
, char *final_date
, bool is_expiry
, bool is_full
, bool is_ascii
) {
1218 char work_date
[9] = { 0x00 };
1219 int len
= is_full
? 8 : 6;
1221 // Copy the data to a working array in the right format
1223 memcpy(work_date
, sprint_hex_inrow((uint8_t *)mrz
+ offset
, len
/ 2), len
);
1225 memcpy(work_date
, mrz
+ offset
, len
);
1228 // Set offset to 0 as we've now copied data.
1232 // If we get the full date, use the first two characters from that for year
1233 memcpy(final_date
, work_date
, 2);
1234 // and do + 2 on offset so that rest of code uses the right data
1237 char temp_year
[3] = { 0x00 };
1238 memcpy(temp_year
, work_date
, 2);
1239 // If it's > 20, assume 19xx.
1240 if (strtol(temp_year
, NULL
, 10) < 20 || is_expiry
) {
1241 final_date
[0] = '2';
1242 final_date
[1] = '0';
1244 final_date
[0] = '1';
1245 final_date
[1] = '9';
1249 memcpy(final_date
+ 2, work_date
+ offset
, 2);
1250 final_date
[4] = '-';
1251 memcpy(final_date
+ 5, work_date
+ offset
+ 2, 2);
1252 final_date
[7] = '-';
1253 memcpy(final_date
+ 8, work_date
+ offset
+ 4, 2);
1256 static void emrtd_print_dob(char *mrz
, int offset
, bool full
, bool ascii
) {
1257 char final_date
[12] = { 0x00 };
1258 emrtd_mrz_convert_date(mrz
, offset
, final_date
, false, full
, ascii
);
1260 PrintAndLogEx(SUCCESS
, "Date of birth.........: " _YELLOW_("%s"), final_date
);
1262 if (!full
&& !emrtd_mrz_verify_check_digit(mrz
, offset
, 6)) {
1263 PrintAndLogEx(SUCCESS
, _RED_("Date of Birth check digit is invalid."));
1267 static void emrtd_print_expiry(char *mrz
, int offset
) {
1268 char final_date
[12] = { 0x00 };
1269 emrtd_mrz_convert_date(mrz
, offset
, final_date
, true, false, true);
1271 PrintAndLogEx(SUCCESS
, "Date of expiry........: " _YELLOW_("%s"), final_date
);
1273 if (!emrtd_mrz_verify_check_digit(mrz
, offset
, 6)) {
1274 PrintAndLogEx(SUCCESS
, _RED_("Date of expiry check digit is invalid."));
1278 static void emrtd_print_issuance(char *data
, bool ascii
) {
1279 char final_date
[12] = { 0x00 };
1280 emrtd_mrz_convert_date(data
, 0, final_date
, true, true, ascii
);
1282 PrintAndLogEx(SUCCESS
, "Date of issue.........: " _YELLOW_("%s"), final_date
);
1285 static void emrtd_print_personalization_timestamp(uint8_t *data
) {
1286 char str_date
[0x0F] = { 0x00 };
1287 strcpy(str_date
, sprint_hex_inrow(data
, 0x0E));
1288 char final_date
[20] = { 0x00 };
1289 sprintf(final_date
, "%.4s-%.2s-%.2s %.2s:%.2s:%.2s", str_date
, str_date
+ 4, str_date
+ 6, str_date
+ 8, str_date
+ 10, str_date
+ 12);
1291 PrintAndLogEx(SUCCESS
, "Personalization at....: " _YELLOW_("%s"), final_date
);
1294 static void emrtd_print_unknown_timestamp_5f85(uint8_t *data
) {
1295 char final_date
[20] = { 0x00 };
1296 sprintf(final_date
, "%.4s-%.2s-%.2s %.2s:%.2s:%.2s", data
, data
+ 4, data
+ 6, data
+ 8, data
+ 10, data
+ 12);
1298 PrintAndLogEx(SUCCESS
, "Unknown timestamp 5F85: " _YELLOW_("%s"), final_date
);
1299 PrintAndLogEx(HINT
, "This is very likely the personalization timestamp, but it is using an undocumented tag.");
1302 static int emrtd_print_ef_com_info(uint8_t *data
, size_t datalen
) {
1303 uint8_t filelist
[50];
1304 size_t filelistlen
= 0;
1305 bool res
= emrtd_lds_get_data_by_tag(data
, datalen
, filelist
, &filelistlen
, 0x5c, 0x00, false, true, 0);
1307 PrintAndLogEx(ERR
, "Failed to read file list from EF_COM.");
1311 // List files in the file list
1312 PrintAndLogEx(NORMAL
, "");
1313 PrintAndLogEx(INFO
, "-------------------- " _CYAN_("EF_COM") " --------------------");
1314 for (int i
= 0; i
< filelistlen
; i
++) {
1315 emrtd_dg_t
*dg
= emrtd_tag_to_dg(filelist
[i
]);
1317 PrintAndLogEx(INFO
, "File tag not found, skipping: %02X", filelist
[i
]);
1320 PrintAndLogEx(SUCCESS
, "%-7s...............: " _YELLOW_("%s"), dg
->filename
, dg
->desc
);
1325 static int emrtd_print_ef_dg1_info(uint8_t *data
, size_t datalen
) {
1328 PrintAndLogEx(NORMAL
, "");
1329 PrintAndLogEx(INFO
, "-------------------- " _CYAN_("EF_DG1") " --------------------");
1331 // MRZ on TD1 is 90 characters, 30 on each row.
1332 // MRZ on TD3 is 88 characters, 44 on each row.
1333 char mrz
[90] = { 0x00 };
1336 if (emrtd_lds_get_data_by_tag(data
, datalen
, (uint8_t *) mrz
, &mrzlen
, 0x5f, 0x1f, true, true, 0) == false) {
1337 PrintAndLogEx(ERR
, "Failed to read MRZ from EF_DG1.");
1341 // Determine and print the document type
1342 if (mrz
[0] == 'I' && mrz
[1] == 'P') {
1343 PrintAndLogEx(SUCCESS
, "Document Type.........: " _YELLOW_("Passport Card"));
1344 } else if (mrz
[0] == 'I') {
1345 PrintAndLogEx(SUCCESS
, "Document Type.........: " _YELLOW_("ID Card"));
1346 } else if (mrz
[0] == 'P') {
1347 PrintAndLogEx(SUCCESS
, "Document Type.........: " _YELLOW_("Passport"));
1348 } else if (mrz
[0] == 'A') {
1349 PrintAndLogEx(SUCCESS
, "Document Type.........: " _YELLOW_("German Residency Permit"));
1351 PrintAndLogEx(SUCCESS
, "Document Type.........: " _YELLOW_("Unknown"));
1356 } else if (mrzlen
== 88) {
1359 PrintAndLogEx(ERR
, "MRZ length (%zu) is wrong.", mrzlen
);
1363 PrintAndLogEx(SUCCESS
, "Document Form Factor..: " _YELLOW_("TD%i"), td_variant
);
1366 if (td_variant
== 1) {
1367 PrintAndLogEx(DEBUG
, "MRZ Row 1: " _YELLOW_("%.30s"), mrz
);
1368 PrintAndLogEx(DEBUG
, "MRZ Row 2: " _YELLOW_("%.30s"), mrz
+ 30);
1369 PrintAndLogEx(DEBUG
, "MRZ Row 3: " _YELLOW_("%.30s"), mrz
+ 60);
1370 } else if (td_variant
== 3) {
1371 PrintAndLogEx(DEBUG
, "MRZ Row 1: " _YELLOW_("%.44s"), mrz
);
1372 PrintAndLogEx(DEBUG
, "MRZ Row 2: " _YELLOW_("%.44s"), mrz
+ 44);
1375 PrintAndLogEx(SUCCESS
, "Issuing state.........: " _YELLOW_("%.3s"), mrz
+ 2);
1377 if (td_variant
== 3) {
1378 // Passport form factor
1379 PrintAndLogEx(SUCCESS
, "Nationality...........: " _YELLOW_("%.3s"), mrz
+ 44 + 10);
1380 emrtd_print_name(mrz
, 5, 38, false);
1381 emrtd_print_document_number(mrz
, 44);
1382 emrtd_print_dob(mrz
, 44 + 13, false, true);
1383 emrtd_print_legal_sex(&mrz
[44 + 20]);
1384 emrtd_print_expiry(mrz
, 44 + 21);
1385 emrtd_print_optional_elements(mrz
, 44 + 28, 14, true);
1387 // Calculate and verify composite check digit
1388 char composite_check_data
[50] = { 0x00 };
1389 memcpy(composite_check_data
, mrz
+ 44, 10);
1390 memcpy(composite_check_data
+ 10, mrz
+ 44 + 13, 7);
1391 memcpy(composite_check_data
+ 17, mrz
+ 44 + 21, 23);
1393 if (!emrtd_compare_check_digit(composite_check_data
, 39, mrz
[87])) {
1394 PrintAndLogEx(SUCCESS
, _RED_("Composite check digit is invalid."));
1396 } else if (td_variant
== 1) {
1398 PrintAndLogEx(SUCCESS
, "Nationality...........: " _YELLOW_("%.3s"), mrz
+ 30 + 15);
1399 emrtd_print_name(mrz
, 60, 30, false);
1400 emrtd_print_document_number(mrz
, 5);
1401 emrtd_print_dob(mrz
, 30, false, true);
1402 emrtd_print_legal_sex(&mrz
[30 + 7]);
1403 emrtd_print_expiry(mrz
, 30 + 8);
1404 emrtd_print_optional_elements(mrz
, 15, 15, false);
1405 emrtd_print_optional_elements(mrz
, 30 + 18, 11, false);
1407 // Calculate and verify composite check digit
1408 if (!emrtd_compare_check_digit(mrz
, 59, mrz
[59])) {
1409 PrintAndLogEx(SUCCESS
, _RED_("Composite check digit is invalid."));
1416 static int emrtd_print_ef_dg11_info(uint8_t *data
, size_t datalen
) {
1417 uint8_t taglist
[100] = { 0x00 };
1418 size_t taglistlen
= 0;
1419 uint8_t tagdata
[1000] = { 0x00 };
1420 size_t tagdatalen
= 0;
1422 PrintAndLogEx(NORMAL
, "");
1423 PrintAndLogEx(INFO
, "-------------------- " _CYAN_("EF_DG11") " -------------------");
1425 if (emrtd_lds_get_data_by_tag(data
, datalen
, taglist
, &taglistlen
, 0x5c, 0x00, false, true, 0) == false) {
1426 PrintAndLogEx(ERR
, "Failed to read file list from EF_DG11.");
1430 for (int i
= 0; i
< taglistlen
; i
++) {
1431 bool res
= emrtd_lds_get_data_by_tag(data
, datalen
, tagdata
, &tagdatalen
, taglist
[i
], taglist
[i
+ 1], taglist
[i
] == 0x5f, true, 0);
1433 // Don't bother with empty tags
1434 if (tagdatalen
== 0) {
1437 // Special behavior for two char tags
1438 if (taglist
[i
] == 0x5f) {
1439 switch (taglist
[i
+ 1]) {
1441 emrtd_print_name((char *) tagdata
, 0, tagdatalen
, true);
1444 emrtd_print_name((char *) tagdata
, 0, tagdatalen
, false);
1447 PrintAndLogEx(SUCCESS
, "Personal Number.......: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1450 // TODO: acc for < separation
1451 PrintAndLogEx(SUCCESS
, "Place of Birth........: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1454 // TODO: acc for < separation
1455 PrintAndLogEx(SUCCESS
, "Permanent Address.....: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1458 PrintAndLogEx(SUCCESS
, "Telephone.............: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1461 PrintAndLogEx(SUCCESS
, "Profession............: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1464 PrintAndLogEx(SUCCESS
, "Title.................: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1467 PrintAndLogEx(SUCCESS
, "Personal Summary......: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1470 saveFile("ProofOfCitizenship", tagdata
[0] == 0xFF ? ".jpg" : ".jp2", tagdata
, tagdatalen
);
1473 // TODO: acc for < separation
1474 PrintAndLogEx(SUCCESS
, "Other valid TDs nums..: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1477 PrintAndLogEx(SUCCESS
, "Custody Information...: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1480 emrtd_print_dob((char *) tagdata
, 0, true, tagdatalen
!= 4);
1483 PrintAndLogEx(SUCCESS
, "Unknown Field %02X%02X....: %s", taglist
[i
], taglist
[i
+ 1], sprint_hex_inrow(tagdata
, tagdatalen
));
1489 // TODO: Account for A0
1490 PrintAndLogEx(SUCCESS
, "Unknown Field %02X......: %s", taglist
[i
], sprint_hex_inrow(tagdata
, tagdatalen
));
1496 static int emrtd_print_ef_dg12_info(uint8_t *data
, size_t datalen
) {
1497 uint8_t taglist
[100] = { 0x00 };
1498 size_t taglistlen
= 0;
1499 uint8_t tagdata
[1000] = { 0x00 };
1500 size_t tagdatalen
= 0;
1502 PrintAndLogEx(NORMAL
, "");
1503 PrintAndLogEx(INFO
, "-------------------- " _CYAN_("EF_DG12") " -------------------");
1505 if (emrtd_lds_get_data_by_tag(data
, datalen
, taglist
, &taglistlen
, 0x5c, 0x00, false, true, 0) == false) {
1506 PrintAndLogEx(ERR
, "Failed to read file list from EF_DG12.");
1510 for (int i
= 0; i
< taglistlen
; i
++) {
1511 bool res
= emrtd_lds_get_data_by_tag(data
, datalen
, tagdata
, &tagdatalen
, taglist
[i
], taglist
[i
+ 1], taglist
[i
] == 0x5f, true, 0);
1513 // Don't bother with empty tags
1514 if (tagdatalen
== 0) {
1517 // Special behavior for two char tags
1518 if (taglist
[i
] == 0x5f) {
1519 // Several things here are longer than the rest but I can't think of a way to shorten them
1520 // ...and I doubt many states are using them.
1521 switch (taglist
[i
+ 1]) {
1523 PrintAndLogEx(SUCCESS
, "Issuing Authority.....: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1526 emrtd_print_issuance((char *) tagdata
, tagdatalen
!= 4);
1529 PrintAndLogEx(SUCCESS
, "Endorsements & Observations: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1532 PrintAndLogEx(SUCCESS
, "Tax/Exit Requirements.: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1535 saveFile("FrontOfDocument", tagdata
[0] == 0xFF ? ".jpg" : ".jp2", tagdata
, tagdatalen
);
1538 saveFile("BackOfDocument", tagdata
[0] == 0xFF ? ".jpg" : ".jp2", tagdata
, tagdatalen
);
1541 emrtd_print_personalization_timestamp(tagdata
);
1544 PrintAndLogEx(SUCCESS
, "Serial of Personalization System: " _YELLOW_("%.*s"), (int)tagdatalen
, tagdata
);
1547 emrtd_print_unknown_timestamp_5f85(tagdata
);
1550 PrintAndLogEx(SUCCESS
, "Unknown Field %02X%02X....: %s", taglist
[i
], taglist
[i
+ 1], sprint_hex_inrow(tagdata
, tagdatalen
));
1556 // TODO: Account for A0
1557 PrintAndLogEx(SUCCESS
, "Unknown Field %02X......: %s", taglist
[i
], sprint_hex_inrow(tagdata
, tagdatalen
));
1563 static int emrtd_ef_sod_extract_signatures(uint8_t *data
, size_t datalen
, uint8_t *dataout
, size_t *dataoutlen
) {
1564 uint8_t top
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
1565 uint8_t signeddata
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
1566 uint8_t emrtdsigcontainer
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
1567 uint8_t emrtdsig
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
1568 uint8_t emrtdsigtext
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
1569 size_t toplen
, signeddatalen
, emrtdsigcontainerlen
, emrtdsiglen
, emrtdsigtextlen
= 0;
1571 if (emrtd_lds_get_data_by_tag(data
, datalen
, top
, &toplen
, 0x30, 0x00, false, true, 0) == false) {
1572 PrintAndLogEx(ERR
, "Failed to read top from EF_SOD.");
1576 PrintAndLogEx(DEBUG
, "top: %s.", sprint_hex_inrow(top
, toplen
));
1578 if (emrtd_lds_get_data_by_tag(top
, toplen
, signeddata
, &signeddatalen
, 0xA0, 0x00, false, false, 0) == false) {
1579 PrintAndLogEx(ERR
, "Failed to read signedData from EF_SOD.");
1583 PrintAndLogEx(DEBUG
, "signeddata: %s.", sprint_hex_inrow(signeddata
, signeddatalen
));
1585 // Do true on reading into the tag as it's a "sequence"
1586 if (emrtd_lds_get_data_by_tag(signeddata
, signeddatalen
, emrtdsigcontainer
, &emrtdsigcontainerlen
, 0x30, 0x00, false, true, 0) == false) {
1587 PrintAndLogEx(ERR
, "Failed to read eMRTDSignature container from EF_SOD.");
1591 PrintAndLogEx(DEBUG
, "emrtdsigcontainer: %s.", sprint_hex_inrow(emrtdsigcontainer
, emrtdsigcontainerlen
));
1593 if (emrtd_lds_get_data_by_tag(emrtdsigcontainer
, emrtdsigcontainerlen
, emrtdsig
, &emrtdsiglen
, 0xA0, 0x00, false, false, 0) == false) {
1594 PrintAndLogEx(ERR
, "Failed to read eMRTDSignature from EF_SOD.");
1598 PrintAndLogEx(DEBUG
, "emrtdsig: %s.", sprint_hex_inrow(emrtdsig
, emrtdsiglen
));
1600 // TODO: Not doing memcpy here, it didn't work, fix it somehow
1601 if (emrtd_lds_get_data_by_tag(emrtdsig
, emrtdsiglen
, emrtdsigtext
, &emrtdsigtextlen
, 0x04, 0x00, false, false, 0) == false) {
1602 PrintAndLogEx(ERR
, "Failed to read eMRTDSignature (text) from EF_SOD.");
1605 memcpy(dataout
, emrtdsigtext
, emrtdsigtextlen
);
1606 *dataoutlen
= emrtdsigtextlen
;
1610 static int emrtd_parse_ef_sod_hash_algo(uint8_t *data
, size_t datalen
, int *hashalgo
) {
1611 uint8_t hashalgoset
[64] = { 0x00 };
1612 size_t hashalgosetlen
= 0;
1614 // We'll return hash algo -1 if we can't find anything
1617 if (emrtd_lds_get_data_by_tag(data
, datalen
, hashalgoset
, &hashalgosetlen
, 0x30, 0x00, false, true, 0) == false) {
1618 PrintAndLogEx(ERR
, "Failed to read hash algo set from EF_SOD.");
1622 PrintAndLogEx(DEBUG
, "hash algo set: %s", sprint_hex_inrow(hashalgoset
, hashalgosetlen
));
1624 // If last two bytes are 05 00, ignore them.
1625 // https://wf.lavatech.top/ave-but-random/emrtd-data-quirks#EF_SOD
1626 if (hashalgoset
[hashalgosetlen
- 2] == 0x05 && hashalgoset
[hashalgosetlen
- 1] == 0x00) {
1627 hashalgosetlen
-= 2;
1630 for (int hashi
= 0; hashalg_table
[hashi
].name
!= NULL
; hashi
++) {
1631 PrintAndLogEx(DEBUG
, "trying: %s", hashalg_table
[hashi
].name
);
1632 // We're only interested in checking if the length matches to avoid memory shenanigans
1633 if (hashalg_table
[hashi
].descriptorlen
!= hashalgosetlen
) {
1634 PrintAndLogEx(DEBUG
, "len mismatch: %zu", hashalgosetlen
);
1638 if (memcmp(hashalg_table
[hashi
].descriptor
, hashalgoset
, hashalgosetlen
) == 0) {
1644 PrintAndLogEx(ERR
, "Failed to parse hash list (Unknown algo: %s). Hash verification won't be available.", sprint_hex_inrow(hashalgoset
, hashalgosetlen
));
1648 static int emrtd_parse_ef_sod_hashes(uint8_t *data
, size_t datalen
, uint8_t *hashes
, int *hashalgo
) {
1649 uint8_t emrtdsig
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
1650 uint8_t hashlist
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
1651 uint8_t hash
[64] = { 0x00 };
1654 uint8_t hashidstr
[4] = { 0x00 };
1655 size_t hashidstrlen
= 0;
1657 size_t emrtdsiglen
= 0;
1658 size_t hashlistlen
= 0;
1661 if (emrtd_ef_sod_extract_signatures(data
, datalen
, emrtdsig
, &emrtdsiglen
) != PM3_SUCCESS
) {
1665 PrintAndLogEx(DEBUG
, "hash data: %s", sprint_hex_inrow(emrtdsig
, emrtdsiglen
));
1667 emrtd_parse_ef_sod_hash_algo(emrtdsig
, emrtdsiglen
, hashalgo
);
1669 if (emrtd_lds_get_data_by_tag(emrtdsig
, emrtdsiglen
, hashlist
, &hashlistlen
, 0x30, 0x00, false, true, 1) == false) {
1670 PrintAndLogEx(ERR
, "Failed to read hash list from EF_SOD.");
1674 PrintAndLogEx(DEBUG
, "hash list: %s", sprint_hex_inrow(hashlist
, hashlistlen
));
1676 while (offset
< hashlistlen
) {
1677 // Get the length of the element
1678 int e_datalen
= emrtd_get_asn1_data_length(hashlist
+ offset
, hashlistlen
- offset
, 1);
1680 // Get the length of the element's length
1681 int e_fieldlen
= emrtd_get_asn1_field_length(hashlist
+ offset
, hashlistlen
- offset
, 1);
1683 switch (hashlist
[offset
]) {
1685 // iceman: if these two calls fails, feels like we should have a better check in place
1686 bool res
= emrtd_lds_get_data_by_tag(hashlist
+ offset
+ e_fieldlen
+ 1, e_datalen
, hashidstr
, &hashidstrlen
, 0x02, 0x00, false, false, 0);
1688 res
= emrtd_lds_get_data_by_tag(hashlist
+ offset
+ e_fieldlen
+ 1, e_datalen
, hash
, &hashlen
, 0x04, 0x00, false, false, 0);
1690 if (hashlen
<= 64) {
1691 memcpy(hashes
+ (hashidstr
[0] * 64), hash
, hashlen
);
1693 PrintAndLogEx(ERR
, "error (emrtd_parse_ef_sod_hashes) hashlen out-of-bounds");
1698 // + 1 for length of ID
1699 offset
+= 1 + e_datalen
+ e_fieldlen
;
1705 static int emrtd_print_ef_sod_info(uint8_t *dg_hashes_calc
, uint8_t *dg_hashes_sod
, int hash_algo
) {
1706 PrintAndLogEx(NORMAL
, "");
1707 PrintAndLogEx(INFO
, "-------------------- " _CYAN_("EF_SOD") " --------------------");
1709 if (hash_algo
== -1) {
1710 PrintAndLogEx(SUCCESS
, "Hash algorithm: " _YELLOW_("Unknown"));
1712 PrintAndLogEx(SUCCESS
, "Hash algorithm: " _YELLOW_("%s"), hashalg_table
[hash_algo
].name
);
1714 uint8_t all_zeroes
[64] = { 0x00 };
1715 for (int i
= 1; i
<= 16; i
++) {
1716 bool calc_all_zero
= (memcmp(dg_hashes_calc
+ (i
* 64), all_zeroes
, hashalg_table
[hash_algo
].hashlen
) == 0);
1717 bool sod_all_zero
= (memcmp(dg_hashes_sod
+ (i
* 64), all_zeroes
, hashalg_table
[hash_algo
].hashlen
) == 0);
1718 bool hash_matches
= (memcmp(dg_hashes_sod
+ (i
* 64), dg_hashes_calc
+ (i
* 64), hashalg_table
[hash_algo
].hashlen
) == 0);
1719 // Ignore files we don't haven't read and lack hashes to
1720 if (calc_all_zero
== true && sod_all_zero
== true) {
1722 } else if (calc_all_zero
== true) {
1723 PrintAndLogEx(SUCCESS
, "EF_DG%i: " _YELLOW_("File couldn't be read, but is in EF_SOD."), i
);
1724 } else if (sod_all_zero
== true) {
1725 PrintAndLogEx(SUCCESS
, "EF_DG%i: " _YELLOW_("File is not in EF_SOD."), i
);
1726 } else if (hash_matches
== false) {
1727 PrintAndLogEx(SUCCESS
, "EF_DG%i: " _RED_("Invalid"), i
);
1729 PrintAndLogEx(SUCCESS
, "EF_DG%i: " _GREEN_("Valid"), i
);
1737 static int emrtd_print_ef_cardaccess_info(uint8_t *data
, size_t datalen
) {
1738 uint8_t dataset
[100] = { 0x00 };
1739 size_t datasetlen
= 0;
1740 uint8_t datafromtag
[100] = { 0x00 };
1741 size_t datafromtaglen
= 0;
1742 uint8_t parsednum
= 0;
1744 PrintAndLogEx(NORMAL
, "");
1745 PrintAndLogEx(INFO
, "----------------- " _CYAN_("EF_CardAccess") " ----------------");
1747 if (emrtd_lds_get_data_by_tag(data
, datalen
, dataset
, &datasetlen
, 0x30, 0x00, false, true, 0) == false) {
1748 PrintAndLogEx(ERR
, "Failed to read set from EF_CardAccess.");
1753 if (emrtd_lds_get_data_by_tag(dataset
, datasetlen
, datafromtag
, &datafromtaglen
, 0x02, 0x00, false, false, 0) == false) {
1754 PrintAndLogEx(ERR
, "Failed to read PACE version from EF_CardAccess.");
1758 memcpy(&parsednum
, datafromtag
, datafromtaglen
);
1759 PrintAndLogEx(SUCCESS
, "PACE version..........: " _YELLOW_("%i"), parsednum
);
1761 // Get PACE algorithm
1762 if (emrtd_lds_get_data_by_tag(dataset
, datasetlen
, datafromtag
, &datafromtaglen
, 0x06, 0x00, false, false, 0) == false) {
1763 PrintAndLogEx(ERR
, "Failed to read PACE algorithm from EF_CardAccess.");
1767 for (int pacei
= 0; pacealg_table
[pacei
].name
!= NULL
; pacei
++) {
1768 PrintAndLogEx(DEBUG
, "Trying: %s", pacealg_table
[pacei
].name
);
1770 if (memcmp(pacealg_table
[pacei
].descriptor
, datafromtag
, datafromtaglen
) == 0) {
1771 PrintAndLogEx(SUCCESS
, "PACE algorithm........: " _YELLOW_("%s"), pacealg_table
[pacei
].name
);
1775 // Get PACE parameter ID
1776 if (emrtd_lds_get_data_by_tag(dataset
, datasetlen
, datafromtag
, &datafromtaglen
, 0x02, 0x00, false, false, 1) == false) {
1777 PrintAndLogEx(ERR
, "Failed to read PACE parameter ID from EF_CardAccess.");
1782 memcpy(&parsednum
, datafromtag
, datafromtaglen
);
1783 for (int pacepari
= 0; pacesdp_table
[pacepari
].id
!= 32; pacepari
++) {
1784 PrintAndLogEx(DEBUG
, "Trying: %s", pacesdp_table
[pacepari
].name
);
1786 if (pacesdp_table
[pacepari
].id
== parsednum
) {
1787 PrintAndLogEx(SUCCESS
, "PACE parameter........: " _YELLOW_("%s"), pacesdp_table
[pacepari
].name
);
1789 // TODO: account for RFU
1795 int infoHF_EMRTD(char *documentnumber
, char *dob
, char *expiry
, bool BAC_available
) {
1796 uint8_t response
[EMRTD_MAX_FILE_SIZE
] = { 0x00 };
1798 uint8_t ssc
[8] = { 0x00 };
1799 uint8_t ks_enc
[16] = { 0x00 };
1800 uint8_t ks_mac
[16] = { 0x00 };
1802 bool PACE_available
= true;
1805 if (emrtd_connect() == false) {
1809 bool use14b
= GetISODEPState() == ISODEP_NFCB
;
1811 // Read EF_CardAccess
1812 if (!emrtd_select_and_read(response
, &resplen
, dg_table
[EF_CardAccess
].fileid
, ks_enc
, ks_mac
, ssc
, BAC
)) {
1813 PACE_available
= false;
1814 PrintAndLogEx(HINT
, "The error above this is normal. It just means that your eMRTD lacks PACE.");
1817 // Select and authenticate with the eMRTD
1818 bool auth_result
= emrtd_do_auth(documentnumber
, dob
, expiry
, BAC_available
, &BAC
, ssc
, ks_enc
, ks_mac
);
1820 PrintAndLogEx(NORMAL
, "");
1821 PrintAndLogEx(INFO
, "------------------ " _CYAN_("Basic Info") " ------------------");
1822 PrintAndLogEx(SUCCESS
, "Communication standard: %s", use14b
? _YELLOW_("ISO/IEC 14443(B)") : _YELLOW_("ISO/IEC 14443(A)"));
1823 PrintAndLogEx(SUCCESS
, "Authentication........: %s", BAC
? _GREEN_("Enforced") : _RED_("Not enforced"));
1824 PrintAndLogEx(SUCCESS
, "PACE..................: %s", PACE_available
? _GREEN_("Available") : _YELLOW_("Not available"));
1825 PrintAndLogEx(SUCCESS
, "Authentication result.: %s", auth_result
? _GREEN_("Successful") : _RED_("Failed"));
1827 if (PACE_available
) {
1828 emrtd_print_ef_cardaccess_info(response
, resplen
);
1836 // Read EF_COM to get file list
1837 if (!emrtd_select_and_read(response
, &resplen
, dg_table
[EF_COM
].fileid
, ks_enc
, ks_mac
, ssc
, BAC
)) {
1838 PrintAndLogEx(ERR
, "Failed to read EF_COM.");
1843 int res
= emrtd_print_ef_com_info(response
, resplen
);
1844 if (res
!= PM3_SUCCESS
) {
1849 uint8_t filelist
[50];
1850 size_t filelistlen
= 0;
1852 if (emrtd_lds_get_data_by_tag(response
, resplen
, filelist
, &filelistlen
, 0x5c, 0x00, false, true, 0) == false) {
1853 PrintAndLogEx(ERR
, "Failed to read file list from EF_COM.");
1858 // Grab the hash list from EF_SOD
1859 uint8_t dg_hashes_sod
[17][64] = { { 0 } };
1860 uint8_t dg_hashes_calc
[17][64] = { { 0 } };
1863 if (!emrtd_select_and_read(response
, &resplen
, dg_table
[EF_SOD
].fileid
, ks_enc
, ks_mac
, ssc
, BAC
)) {
1864 PrintAndLogEx(ERR
, "Failed to read EF_SOD.");
1869 res
= emrtd_parse_ef_sod_hashes(response
, resplen
, *dg_hashes_sod
, &hash_algo
);
1870 if (res
!= PM3_SUCCESS
) {
1871 PrintAndLogEx(ERR
, "Failed to read hash list from EF_SOD. Hash checks will fail.");
1874 // Dump all files in the file list
1875 for (int i
= 0; i
< filelistlen
; i
++) {
1876 emrtd_dg_t
*dg
= emrtd_tag_to_dg(filelist
[i
]);
1878 PrintAndLogEx(INFO
, "File tag not found, skipping: %02X", filelist
[i
]);
1881 if (dg
->fastdump
&& !dg
->pace
&& !dg
->eac
) {
1882 if (emrtd_select_and_read(response
, &resplen
, dg
->fileid
, ks_enc
, ks_mac
, ssc
, BAC
)) {
1883 if (dg
->parser
!= NULL
)
1884 dg
->parser(response
, resplen
);
1886 PrintAndLogEx(DEBUG
, "EF_DG%i hash algo: %i", dg
->dgnum
, hash_algo
);
1888 if (hash_algo
!= -1) {
1889 PrintAndLogEx(DEBUG
, "EF_DG%i hash on EF_SOD: %s", dg
->dgnum
, sprint_hex_inrow(dg_hashes_sod
[dg
->dgnum
], hashalg_table
[hash_algo
].hashlen
));
1890 hashalg_table
[hash_algo
].hasher(response
, resplen
, dg_hashes_calc
[dg
->dgnum
]);
1891 PrintAndLogEx(DEBUG
, "EF_DG%i hash calc: %s", dg
->dgnum
, sprint_hex_inrow(dg_hashes_calc
[dg
->dgnum
], hashalg_table
[hash_algo
].hashlen
));
1898 emrtd_print_ef_sod_info(*dg_hashes_calc
, *dg_hashes_sod
, hash_algo
);
1903 int infoHF_EMRTD_offline(const char *path
) {
1906 char *filepath
= calloc(strlen(path
) + 100, sizeof(char));
1907 if (filepath
== NULL
)
1909 strcpy(filepath
, path
);
1910 strncat(filepath
, PATHSEP
, 2);
1911 strcat(filepath
, dg_table
[EF_COM
].filename
);
1913 if (loadFile_safeEx(filepath
, ".BIN", (void **)&data
, (size_t *)&datalen
, false) != PM3_SUCCESS
) {
1914 PrintAndLogEx(ERR
, "Failed to read EF_COM.");
1919 int res
= emrtd_print_ef_com_info(data
, datalen
);
1920 if (res
!= PM3_SUCCESS
) {
1926 uint8_t filelist
[50];
1927 size_t filelistlen
= 0;
1928 res
= emrtd_lds_get_data_by_tag(data
, datalen
, filelist
, &filelistlen
, 0x5c, 0x00, false, true, 0);
1930 PrintAndLogEx(ERR
, "Failed to read file list from EF_COM.");
1937 // Grab the hash list
1938 uint8_t dg_hashes_sod
[17][64] = { { 0 } };
1939 uint8_t dg_hashes_calc
[17][64] = { { 0 } };
1942 strcpy(filepath
, path
);
1943 strncat(filepath
, PATHSEP
, 2);
1944 strcat(filepath
, dg_table
[EF_CardAccess
].filename
);
1946 if (loadFile_safeEx(filepath
, ".BIN", (void **)&data
, (size_t *)&datalen
, false) == PM3_SUCCESS
) {
1947 emrtd_print_ef_cardaccess_info(data
, datalen
);
1949 PrintAndLogEx(HINT
, "The error above this is normal. It just means that your eMRTD lacks PACE.");
1952 strcpy(filepath
, path
);
1953 strncat(filepath
, PATHSEP
, 2);
1954 strcat(filepath
, dg_table
[EF_SOD
].filename
);
1956 if (loadFile_safeEx(filepath
, ".BIN", (void **)&data
, (size_t *)&datalen
, false) != PM3_SUCCESS
) {
1957 PrintAndLogEx(ERR
, "Failed to read EF_SOD.");
1962 res
= emrtd_parse_ef_sod_hashes(data
, datalen
, *dg_hashes_sod
, &hash_algo
);
1963 if (res
!= PM3_SUCCESS
) {
1964 PrintAndLogEx(ERR
, "Failed to read hash list from EF_SOD. Hash checks will fail.");
1968 // Read files in the file list
1969 for (int i
= 0; i
< filelistlen
; i
++) {
1970 emrtd_dg_t
*dg
= emrtd_tag_to_dg(filelist
[i
]);
1972 PrintAndLogEx(INFO
, "File tag not found, skipping: %02X", filelist
[i
]);
1975 if (!dg
->pace
&& !dg
->eac
) {
1976 strcpy(filepath
, path
);
1977 strncat(filepath
, PATHSEP
, 2);
1978 strcat(filepath
, dg
->filename
);
1979 if (loadFile_safeEx(filepath
, ".BIN", (void **)&data
, (size_t *)&datalen
, false) == PM3_SUCCESS
) {
1980 // we won't halt on parsing errors
1981 if (dg
->parser
!= NULL
)
1982 dg
->parser(data
, datalen
);
1984 PrintAndLogEx(DEBUG
, "EF_DG%i hash algo: %i", dg
->dgnum
, hash_algo
);
1986 if (hash_algo
!= -1) {
1987 PrintAndLogEx(DEBUG
, "EF_DG%i hash on EF_SOD: %s", dg
->dgnum
, sprint_hex_inrow(dg_hashes_sod
[dg
->dgnum
], hashalg_table
[hash_algo
].hashlen
));
1988 hashalg_table
[hash_algo
].hasher(data
, datalen
, dg_hashes_calc
[dg
->dgnum
]);
1989 PrintAndLogEx(DEBUG
, "EF_DG%i hash calc: %s", dg
->dgnum
, sprint_hex_inrow(dg_hashes_calc
[dg
->dgnum
], hashalg_table
[hash_algo
].hashlen
));
1997 emrtd_print_ef_sod_info(*dg_hashes_calc
, *dg_hashes_sod
, hash_algo
);
2002 static void text_to_upper(uint8_t *data
, int datalen
) {
2003 // Loop over text to make lowercase text uppercase
2004 for (int i
= 0; i
< datalen
; i
++) {
2005 data
[i
] = toupper(data
[i
]);
2009 static bool validate_date(uint8_t *data
, int datalen
) {
2010 // Date has to be 6 chars
2015 // Check for valid date and month numbers
2016 char temp
[4] = { 0x00 };
2017 memcpy(temp
, data
+ 2, 2);
2018 int month
= (int) strtol(temp
, NULL
, 10);
2019 memcpy(temp
, data
+ 4, 2);
2020 int day
= (int) strtol(temp
, NULL
, 10);
2022 return !(day
<= 0 || day
> 31 || month
<= 0 || month
> 12);
2025 static int CmdHFeMRTDDump(const char *Cmd
) {
2026 CLIParserContext
*ctx
;
2027 CLIParserInit(&ctx
, "hf emrtd dump",
2028 "Dump all files on an eMRTD",
2032 void *argtable
[] = {
2034 arg_str0("n", "documentnumber", "<alphanum>", "document number, up to 9 chars"),
2035 arg_str0("d", "dateofbirth", "<YYMMDD>", "date of birth in YYMMDD format"),
2036 arg_str0("e", "expiry", "<YYMMDD>", "expiry in YYMMDD format"),
2037 arg_str0("m", "mrz", "<[0-9A-Z<]>", "2nd line of MRZ, 44 chars"),
2038 arg_str0(NULL
, "path", "<dirpath>", "save dump to the given dirpath"),
2041 CLIExecWithReturn(ctx
, Cmd
, argtable
, true);
2043 uint8_t mrz
[45] = { 0x00 };
2044 uint8_t docnum
[10] = { 0x00 };
2045 uint8_t dob
[7] = { 0x00 };
2046 uint8_t expiry
[7] = { 0x00 };
2050 // Go through all args, if even one isn't supplied, mark BAC as unavailable
2051 if (CLIParamStrToBuf(arg_get_str(ctx
, 1), docnum
, 9, &slen
) != 0 || slen
== 0) {
2054 text_to_upper(docnum
, slen
);
2057 memset(docnum
+ slen
, '<', 9 - slen
);
2061 if (CLIParamStrToBuf(arg_get_str(ctx
, 2), dob
, 6, &slen
) != 0 || slen
== 0) {
2064 if (!validate_date(dob
, slen
)) {
2065 PrintAndLogEx(ERR
, "Date of birth date format is incorrect, cannot continue.");
2066 PrintAndLogEx(HINT
, "Use the format YYMMDD.");
2071 if (CLIParamStrToBuf(arg_get_str(ctx
, 3), expiry
, 6, &slen
) != 0 || slen
== 0) {
2074 if (!validate_date(expiry
, slen
)) {
2075 PrintAndLogEx(ERR
, "Expiry date format is incorrect, cannot continue.");
2076 PrintAndLogEx(HINT
, "Use the format YYMMDD.");
2081 if (CLIParamStrToBuf(arg_get_str(ctx
, 4), mrz
, 44, &slen
) == 0 && slen
!= 0) {
2083 PrintAndLogEx(ERR
, "MRZ length is incorrect, it should be 44, not %i", slen
);
2087 text_to_upper(mrz
, slen
);
2088 memcpy(docnum
, &mrz
[0], 9);
2089 memcpy(dob
, &mrz
[13], 6);
2090 memcpy(expiry
, &mrz
[21], 6);
2091 // TODO check MRZ checksums?
2092 if (!validate_date(dob
, 6)) {
2093 PrintAndLogEx(ERR
, "Date of birth date format is incorrect, cannot continue.");
2094 PrintAndLogEx(HINT
, "Use the format YYMMDD.");
2097 if (!validate_date(expiry
, 6)) {
2098 PrintAndLogEx(ERR
, "Expiry date format is incorrect, cannot continue.");
2099 PrintAndLogEx(HINT
, "Use the format YYMMDD.");
2105 uint8_t path
[FILENAME_MAX
] = { 0x00 };
2106 if (CLIParamStrToBuf(arg_get_str(ctx
, 5), path
, sizeof(path
), &slen
) != 0 || slen
== 0) {
2114 bool restore_apdu_logging
= GetAPDULogging();
2115 if (g_debugMode
>= 2) {
2116 SetAPDULogging(true);
2118 int res
= dumpHF_EMRTD((char *)docnum
, (char *)dob
, (char *)expiry
, BAC
, (const char *)path
);
2119 SetAPDULogging(restore_apdu_logging
);
2123 static int CmdHFeMRTDInfo(const char *Cmd
) {
2124 CLIParserContext
*ctx
;
2125 CLIParserInit(&ctx
, "hf emrtd info",
2126 "Display info about an eMRTD",
2130 void *argtable
[] = {
2132 arg_str0("n", "documentnumber", "<alphanum>", "document number, up to 9 chars"),
2133 arg_str0("d", "dateofbirth", "<YYMMDD>", "date of birth in YYMMDD format"),
2134 arg_str0("e", "expiry", "<YYMMDD>", "expiry in YYMMDD format"),
2135 arg_str0("m", "mrz", "<[0-9A-Z<]>", "2nd line of MRZ, 44 chars (passports only)"),
2136 arg_str0(NULL
, "path", "<dirpath>", "display info from offline dump stored in dirpath"),
2139 CLIExecWithReturn(ctx
, Cmd
, argtable
, true);
2141 uint8_t mrz
[45] = { 0x00 };
2142 uint8_t docnum
[10] = { 0x00 };
2143 uint8_t dob
[7] = { 0x00 };
2144 uint8_t expiry
[7] = { 0x00 };
2148 // Go through all args, if even one isn't supplied, mark BAC as unavailable
2149 if (CLIParamStrToBuf(arg_get_str(ctx
, 1), docnum
, 9, &slen
) != 0 || slen
== 0) {
2152 text_to_upper(docnum
, slen
);
2154 memset(docnum
+ slen
, '<', 9 - slen
);
2158 if (CLIParamStrToBuf(arg_get_str(ctx
, 2), dob
, 6, &slen
) != 0 || slen
== 0) {
2161 if (!validate_date(dob
, slen
)) {
2162 PrintAndLogEx(ERR
, "Date of birth date format is incorrect, cannot continue.");
2163 PrintAndLogEx(HINT
, "Use the format YYMMDD.");
2168 if (CLIParamStrToBuf(arg_get_str(ctx
, 3), expiry
, 6, &slen
) != 0 || slen
== 0) {
2171 if (!validate_date(expiry
, slen
)) {
2172 PrintAndLogEx(ERR
, "Expiry date format is incorrect, cannot continue.");
2173 PrintAndLogEx(HINT
, "Use the format YYMMDD.");
2178 if (CLIParamStrToBuf(arg_get_str(ctx
, 4), mrz
, 44, &slen
) == 0 && slen
!= 0) {
2180 PrintAndLogEx(ERR
, "MRZ length is incorrect, it should be 44, not %i", slen
);
2184 text_to_upper(mrz
, slen
);
2185 memcpy(docnum
, &mrz
[0], 9);
2186 memcpy(dob
, &mrz
[13], 6);
2187 memcpy(expiry
, &mrz
[21], 6);
2188 // TODO check MRZ checksums?
2189 if (!validate_date(dob
, 6)) {
2190 PrintAndLogEx(ERR
, "Date of birth date format is incorrect, cannot continue.");
2191 PrintAndLogEx(HINT
, "Use the format YYMMDD.");
2194 if (!validate_date(expiry
, 6)) {
2195 PrintAndLogEx(ERR
, "Expiry date format is incorrect, cannot continue.");
2196 PrintAndLogEx(HINT
, "Use the format YYMMDD.");
2201 uint8_t path
[FILENAME_MAX
] = { 0x00 };
2202 bool offline
= CLIParamStrToBuf(arg_get_str(ctx
, 5), path
, sizeof(path
), &slen
) == 0 && slen
> 0;
2208 return infoHF_EMRTD_offline((const char *)path
);
2210 bool restore_apdu_logging
= GetAPDULogging();
2211 if (g_debugMode
>= 2) {
2212 SetAPDULogging(true);
2214 int res
= infoHF_EMRTD((char *)docnum
, (char *)dob
, (char *)expiry
, BAC
);
2215 SetAPDULogging(restore_apdu_logging
);
2220 static int CmdHFeMRTDList(const char *Cmd
) {
2221 return CmdTraceListAlias(Cmd
, "hf emrtd", "7816");
2224 static command_t CommandTable
[] = {
2225 {"help", CmdHelp
, AlwaysAvailable
, "This help"},
2226 {"dump", CmdHFeMRTDDump
, IfPm3Iso14443
, "Dump eMRTD files to binary files"},
2227 {"info", CmdHFeMRTDInfo
, AlwaysAvailable
, "Display info about an eMRTD"},
2228 {"list", CmdHFeMRTDList
, AlwaysAvailable
, "List ISO 14443A/7816 history"},
2229 {NULL
, NULL
, NULL
, NULL
}
2232 static int CmdHelp(const char *Cmd
) {
2233 (void)Cmd
; // Cmd is not used so far
2234 CmdsHelp(CommandTable
);
2238 int CmdHFeMRTD(const char *Cmd
) {
2239 clearCommandBuffer();
2240 return CmdsParse(CommandTable
, Cmd
);