diff --git a/src/iso7816_strings.c b/src/iso7816_strings.c index 5f0d681..9f5aaeb 100644 --- a/src/iso7816_strings.c +++ b/src/iso7816_strings.c @@ -310,6 +310,98 @@ static const char* iso7816_capdu_read_record_get_string( } } +static const char* iso7816_capdu_put_data_get_string( + const uint8_t* c_apdu, + size_t c_apdu_len, + char* str, + size_t str_len +) +{ + enum iso7816_apdu_case_t apdu_case; + struct iso7816_apdu_case_3s_t* apdu_case_3s; + uint8_t P1; + uint8_t P2; + + if ((c_apdu[0] & ISO7816_CLA_PROPRIETARY) != 0 || + (c_apdu[1] != 0xDA && c_apdu[1] != 0xDB) + ) { + // Not PUT DATA + return NULL; + } + + // PUT DATA must be APDU case 3S/3E but only support 3S for now + // See ISO 7816-4:2005, 7.4.3, table 64 + apdu_case = iso7816_apdu_case(c_apdu, c_apdu_len); + switch (apdu_case) { + case ISO7816_APDU_CASE_3S: + apdu_case_3s = (void*)c_apdu; + break; + + case ISO7816_APDU_CASE_3E: + // Unsupported APDU case for PUT DATA + snprintf(str, str_len, "PUT DATA"); + return str; + + default: + // Invalid APDU case for PUT DATA + return NULL; + } + + // Extract P1 and P2 for convenience + P1 = apdu_case_3s->P1; + P2 = apdu_case_3s->P2; + + // Decode P1 and P2 + // See ISO 7816-4:2005, 7.4.1, table 62 + if ((c_apdu[1] & 0x01) == 0) { + // Even INS code + if (P1 == 0 && P2 >= 0x40 && P2 <= 0xFE) { + // P2 is 1-byte BER-TLV tag + snprintf(str, str_len, "PUT DATA for BER-TLV field %02X", P2); + return str; + } else if (P1 == 1) { + // P2 is proprietary + snprintf(str, str_len, "PUT DATA for proprietary identifier %02X", P2); + return str; + } else if (P1 == 2 && P2 >= 0x01 && P2 <= 0xFE) { + // P2 is 1-byte SIMPLE-TLV tag + snprintf(str, str_len, "PUT DATA for SIMPLE-TLV field %02X", P2); + return str; + } else if (P1 >= 0x40) { + // P1-P2 is 2-byte BER-TLV tag + snprintf(str, str_len, "PUT DATA for BER-TLV field %02X%02X", P1, P2); + return str; + } + } else { + // Odd INS code + if (P1 == 0x00 && (P2 & 0xE0) == 0x00 && // Highest 11 bits of P1-P2 are unset + (P2 & 0x1F) != 0x00 && (P2 & 0x1F) != 0x1F // Lowest 5 bits are not equal + ) { + // P2 is short EF identifier + snprintf(str, str_len, "PUT DATA for short EF %u", P2); + return str; + } else if (P1 == 0x3F && P2 == 0xFF) { // P1-P2 is 3FFF + // P1-P2 is current DF + snprintf(str, str_len, "PUT DATA for current DF"); + return str; + } else if (P1 == 0x00 && P2 == 0x00 && // P1-P2 is 0000 + apdu_case_3s->Lc == 0 // No command data + ) { + // P1-P2 is current EF + snprintf(str, str_len, "PUT DATA for current EF"); + return str; + } else if (P1 != 0x00 || P2 != 0x00) { // P1-P2 is not 0000 + // P1-P2 is file identifier + snprintf(str, str_len, "PUT DATA for file %02X%02X", P1, P2); + return str; + } + } + + // Unknown P1 and P2 + snprintf(str, str_len, "PUT DATA"); + return str; +} + const char* iso7816_capdu_get_string( const void* c_apdu, size_t c_apdu_len, @@ -386,7 +478,7 @@ const char* iso7816_capdu_get_string( case 0xD6: case 0xD7: ins_str = "UPDATE BINARY"; break; case 0xDA: - case 0xDB: ins_str = "PUT DATA"; break; + case 0xDB: return iso7816_capdu_put_data_get_string(c_apdu, c_apdu_len, str, str_len); case 0xDC: case 0xDD: ins_str = "UPDATE RECORD"; break; case 0xE0: ins_str = "CREATE FILE"; break; diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 544c80c..ef0063b 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -607,7 +607,7 @@ if(TARGET emv-decode AND BUILD_TESTING) string(CONCAT emv_decode_issuer_response_test2_regex "^89 \\| Authorisation Code : \\[6\\] 36 32 37 31 32 33 \"627123\"[\r\n]" "72 \\| Issuer Script Template 2 : \\[17\\][\r\n]" - " 86 \\| Issuer Script Command : \\[15\\] 04 DA 9F 6B 0A 00 00 00 05 00 00 91 98 63 52 \\(PUT DATA\\)[\r\n]" + " 86 \\| Issuer Script Command : \\[15\\] 04 DA 9F 6B 0A 00 00 00 05 00 00 91 98 63 52 \\(PUT DATA for BER-TLV field 9F6B\\)[\r\n]" "8A \\| Authorisation Response Code : \\[2\\] 30 30 \"00 - Approved or completed successfully\"[\r\n]" "91 \\| Issuer Authentication Data : \\[8\\] A5 84 F9 49 00 82 00 00[\r\n]" " - Authorisation Response Cryptogram \\(ARPC\\): A584F949[\r\n]" @@ -644,7 +644,7 @@ if(TARGET emv-decode AND BUILD_TESTING) "^72 \\| Issuer Script Template 2 : \\[69\\][\r\n]" " 9F18 \\| Issuer Script Identifier : \\[4\\] 80 00 00 00[\r\n]" " 86 \\| Issuer Script Command : \\[21\\] 84 24 00 02 10 FE BF 34 F0 0B 7C E7 70 DC 61 DA 84 7B FB 1E 59 \\(PIN CHANGE/UNBLOCK\\)[\r\n]" - " 86 \\| Issuer Script Command : \\[37\\] 04 DA 8E 00 20 00 00 00 00 00 00 00 00 42 01 41 03 5E 03 1F 02 00 00 00 00 00 00 00 00 AC 7F 4D F1 D6 24 A0 ED \\(PUT DATA\\)[\r\n]" + " 86 \\| Issuer Script Command : \\[37\\] 04 DA 8E 00 20 00 00 00 00 00 00 00 00 42 01 41 03 5E 03 1F 02 00 00 00 00 00 00 00 00 AC 7F 4D F1 D6 24 A0 ED \\(PUT DATA for BER-TLV field 8E00\\)[\r\n]" ) set_tests_properties(emv_decode_issuer_response_test4 PROPERTIES @@ -659,7 +659,7 @@ if(TARGET emv-decode AND BUILD_TESTING) string(CONCAT emv_decode_issuer_response_test5_regex "^72 \\| Issuer Script Template 2 : \\[23\\][\r\n]" " 9F18 \\| Issuer Script Identifier : \\[4\\] 00 00 40 00[\r\n]" - " 86 \\| Issuer Script Command : \\[14\\] 04 DA 9F 58 09 00 C7 35 62 86 E3 77 98 89 \\(PUT DATA\\)[\r\n]" + " 86 \\| Issuer Script Command : \\[14\\] 04 DA 9F 58 09 00 C7 35 62 86 E3 77 98 89 \\(PUT DATA for BER-TLV field 9F58\\)[\r\n]" ) set_tests_properties(emv_decode_issuer_response_test5 PROPERTIES