From a47bdebc87bb3695664d65fa9b8bcfbec4b4bc60 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Mon, 31 Aug 2026 15:28:49 +0800 Subject: [PATCH] Extended T=1 support Extended T=1 support with TA1=95 and T1_IFSD_WANTED 254 --- armsrc/i2c.c | 36 +++++++++++- armsrc/i2c.h | 21 +++++++ armsrc/sam_common.c | 90 ++++++++++++++++++++++++----- armsrc/sam_sc.c | 3 + client/resources/sim023.bin | Bin 0 -> 7160 bytes client/resources/sim023.sha512.txt | 1 + 6 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 client/resources/sim023.bin create mode 100644 client/resources/sim023.sha512.txt diff --git a/armsrc/i2c.c b/armsrc/i2c.c index cd57934fa..d9bac2f7e 100644 --- a/armsrc/i2c.c +++ b/armsrc/i2c.c @@ -65,6 +65,13 @@ static bool s_proto_announced = false; // keep the T=0 they had before. static uint8_t s_pps_proto_cmd = 0; +#if SAM_SC_FORCE_T1_TA1_95 +// One-shot request used by the SAM secure-channel path. Keep it separate +// from generic SmartCardRaw PPS selection so the performance policy does not +// alter unrelated contact-card commands. +static bool s_sam_t1_profile_requested = false; +#endif + // A negotiated rate lives in two places that reset independently: the module's // UART divisor, which any I2C_Reset_EnterMainProgram() wipes, and the card, // which only an RST pulse clears. Left alone the two drift apart and every @@ -1143,6 +1150,12 @@ uint8_t sc_active_device_cmd(void) { return (s_pps_proto_cmd != 0) ? s_pps_proto_cmd : I2C_DEVICE_CMD_SEND_T0; } +void sc_request_sam_t1_profile(void) { +#if SAM_SC_FORCE_T1_TA1_95 + s_sam_t1_profile_requested = true; +#endif +} + void sc_pps_remember(const uint8_t *atr, uint8_t atr_len, uint8_t proto, uint8_t ta1) { if ((atr_len == 0) || (atr_len > sizeof(s_pps.atr))) { return; @@ -1217,6 +1230,15 @@ bool GetATR(smart_card_atr_t *card_ptr, bool verbose) { s_card_protocols = atr_protocols(card_ptr->atr, card_ptr->atr_len); s_proto_announced = false; +#if SAM_SC_FORCE_T1_TA1_95 + const bool request_sam_t1 = s_sam_t1_profile_requested; + s_sam_t1_profile_requested = false; + if (request_sam_t1) { + // This reset starts a fresh SAM session. Do not restore an older + // cached T=0 PPS entry before the one PPS below selects T=1. + sc_pps_forget(); + } +#endif if (g_dbglevel >= DBG_INFO) { // What the ATR advertises, and which of them the card actually runs // until something negotiates otherwise. Saying only "offers T=0 T=1" @@ -1264,17 +1286,25 @@ bool GetATR(smart_card_atr_t *card_ptr, bool verbose) { // once - a refusal is remembered so every later ATR does not retry it. if (s_pps.reapply && (s_pps.tried == false)) { + uint8_t want_proto = atr_first_proto(card_ptr->atr, card_ptr->atr_len); uint8_t want = sc_pps_best_ta1(card_ptr->atr, card_ptr->atr_len); +#if SAM_SC_FORCE_T1_TA1_95 + if (request_sam_t1 && (s_card_protocols & SC_PROTO_T1)) { + want_proto = 1; + want = SAM_SC_T1_TA1; + } +#endif + memcpy(s_pps.atr, card_ptr->atr, card_ptr->atr_len); s_pps.atr_len = card_ptr->atr_len; s_pps.tried = true; - if (want && sc_pps(atr_first_proto(card_ptr->atr, card_ptr->atr_len), want)) { + if (want && sc_pps(want_proto, want)) { s_pps.ta1 = want; - s_pps.proto = atr_first_proto(card_ptr->atr, card_ptr->atr_len); + s_pps.proto = want_proto; if (g_dbglevel >= DBG_INFO) { - Dbprintf("SC: negotiated TA1 %02X", want); + Dbprintf("SC: negotiated T=%u TA1 %02X", want_proto, want); } } } diff --git a/armsrc/i2c.h b/armsrc/i2c.h index c40f94653..828f95440 100644 --- a/armsrc/i2c.h +++ b/armsrc/i2c.h @@ -34,6 +34,15 @@ // LRC/CRC - the same way SEND_T0 handles the T=0 procedure bytes. #define I2C_DEVICE_CMD_SEND_T1 0x08 #define I2C_DEVICE_CMD_PPS 0x09 +// SIM module firmware v4.65 and up. A compatibility alias for T=0 in the +// current SIM_C build. Grace response assembly remains PM3-side. +#define I2C_DEVICE_CMD_SEND_T0_AUTORESP 0x0A + +// Disabled: live testing showed that opcode 0x0A itself is not reliable on the +// PM3<->SIM I2C path, even when the SIM handler is an exact SEND_T0 pass-through. +// Keep T=0 traffic on the established 0x07 transport until that low-level issue +// is understood. +#define SAM_T0_AUTORESP 0 // SIM module firmware versions this build knows about. // @@ -46,6 +55,13 @@ #define SIM_MODULE_VERS_T1_HI 4 #define SIM_MODULE_VERS_T1_LO 51 +// SAM secure-channel transport policy for the performance build. Artemis +// offers T=0 first in its ATR; request its advertised T=1 service explicitly +// and use the validated Fi=512/Di=16 rate. This is protocol selection only: +// it does not enable APDU dumps or other bring-up diagnostics. +#define SAM_SC_FORCE_T1_TA1_95 1 +#define SAM_SC_T1_TA1 0x95 + // The SIM module v4 supports up to 384 bytes for the length. #define ISO7816_MAX_FRAME 270 @@ -117,6 +133,11 @@ uint8_t sc_raw_device_cmd(smartcard_command_t flags); // otherwise. uint8_t sc_active_device_cmd(void); +// Request the configured T=1 profile for the next SAM-only GetATR(). The +// request is consumed once, so unrelated SmartCardRaw traffic still follows +// its own ATR/PPS policy. +void sc_request_sam_t1_profile(void); + // Log one smartcard frame, timestamped from the tick counter. Start is where // the previous frame ended, so a Tag frame's span is how long the card took to // answer and a Rdr frame's is how long the host took to ask. diff --git a/armsrc/sam_common.c b/armsrc/sam_common.c index 93804dd27..309ccb655 100644 --- a/armsrc/sam_common.c +++ b/armsrc/sam_common.c @@ -204,7 +204,19 @@ int sam_rxtx(const uint8_t *data, uint16_t n, uint8_t *resp, uint16_t *resplen) // Whatever protocol GetATR()/PPS left the card on, rather than an assumed // T=0. Resolved once per exchange so the GET RESPONSE round below cannot // end up on a different protocol than the command it belongs to. - const uint8_t dev_cmd = sc_active_device_cmd(); + const uint8_t active_cmd = sc_active_device_cmd(); + const bool t1 = (active_cmd == I2C_DEVICE_CMD_SEND_T1); + // Use the v4.65+ compatibility T=0 opcode when available. Grace responses + // still assemble their 61xx/9Fxx continuations below on the PM3: they may + // carry material response data before the continuation status. T=1 already + // returns its whole APDU response through the module's block layer. +#if SAM_T0_AUTORESP + const uint8_t dev_cmd = (active_cmd == I2C_DEVICE_CMD_SEND_T0) + ? I2C_DEVICE_CMD_SEND_T0_AUTORESP + : active_cmd; +#else + const uint8_t dev_cmd = active_cmd; +#endif bool res = I2C_BufferWrite(data, n, dev_cmd, I2C_DEVICE_ADDRESS_MAIN); if (res == false) { @@ -261,10 +273,21 @@ int sam_rxtx(const uint8_t *data, uint16_t n, uint8_t *resp, uint16_t *resplen) *resplen = 0; } - uint8_t cmd_getresp[] = {0x00, ISO7816_GET_RESPONSE, 0x00, 0x00, more_len}; - sc_log_trace(cmd_getresp, sizeof(cmd_getresp), true); + // Grace T=1 exchanges use extended APDUs. The normal short T=0 GET + // RESPONSE is invalid on that path, so use an extended Le when a T=1 + // response explicitly asks for more data. + uint8_t cmd_getresp_t0[] = {0x00, ISO7816_GET_RESPONSE, 0x00, 0x00, more_len}; + uint16_t want = more_len ? more_len : 256; + uint8_t cmd_getresp_t1[] = {0x00, ISO7816_GET_RESPONSE, 0x00, 0x00, + 0x00, (uint8_t)(want >> 8), (uint8_t)want}; + const uint8_t *cmd_getresp = t1 ? cmd_getresp_t1 : cmd_getresp_t0; + const uint16_t cmd_getresp_len = t1 ? sizeof(cmd_getresp_t1) : sizeof(cmd_getresp_t0); + sc_log_trace(cmd_getresp, cmd_getresp_len, true); - res = I2C_BufferWrite(cmd_getresp, sizeof(cmd_getresp), dev_cmd, I2C_DEVICE_ADDRESS_MAIN); + // Keep response assembly at the PM3, and use the ordinary active protocol + // opcode for the continuation rather than recursively selecting the + // compatibility alias. + res = I2C_BufferWrite(cmd_getresp, cmd_getresp_len, active_cmd, I2C_DEVICE_ADDRESS_MAIN); if (res == false) { DbpString("failed to send to SIM CARD 2"); goto out; @@ -341,29 +364,52 @@ int sam_send_payload_ex( int res = PM3_SUCCESS; uint8_t *buf = response; + const uint16_t inner_len = (uint16_t)(SAM_TX_ASN1_PREFIX_LENGTH + *payload_len); + const bool t1 = (sc_active_device_cmd() == I2C_DEVICE_CMD_SEND_T1); + uint16_t payload_offset = SAM_TX_APDU_PREFIX_LENGTH; + + if ((uint32_t)inner_len + (t1 ? 9u : 5u) > ISO7816_MAX_FRAME) { + return PM3_EINVARG; + } + if (!t1 && inner_len > 0xff) { + return PM3_EINVARG; + } buf[0] = 0xA0; // CLA buf[1] = 0xDA; // INS (PUT DATA) buf[2] = 0x02; // P1 (TLV format?) buf[3] = 0x63; // P2 - buf[4] = SAM_TX_ASN1_PREFIX_LENGTH + (uint8_t) * payload_len; // LEN + if (t1) { + // The Artemis T=1 service accepts Grace in extended APDU form. This + // matches the working ACR39U exchange: 00 ... 0000. + buf[4] = 0x00; + buf[5] = (uint8_t)(inner_len >> 8); + buf[6] = (uint8_t)inner_len; + payload_offset = 7; + } else { + buf[4] = (uint8_t)inner_len; + } // Grace routing header: FROM, TO, REPLY-TO, 0x00, 0x00, scFlag - buf[5] = addr_src; - buf[6] = addr_dest; - buf[7] = addr_reply; + buf[payload_offset] = addr_src; + buf[payload_offset + 1] = addr_dest; + buf[payload_offset + 2] = addr_reply; - buf[8] = 0x00; - buf[9] = 0x00; - buf[10] = scFlag; + buf[payload_offset + 3] = 0x00; + buf[payload_offset + 4] = 0x00; + buf[payload_offset + 5] = scFlag; memcpy( - &buf[11], + &buf[payload_offset + SAM_TX_ASN1_PREFIX_LENGTH], payload, *payload_len ); - uint16_t length = SAM_TX_ASN1_PREFIX_LENGTH + SAM_TX_APDU_PREFIX_LENGTH + (uint8_t) * payload_len; + uint16_t length = (uint16_t)(payload_offset + inner_len); + if (t1) { + buf[length++] = 0x00; // extended Le = 65536 (maximum response) + buf[length++] = 0x00; + } sc_log_trace(buf, length, true); if (g_dbglevel >= DBG_INFO) { @@ -413,7 +459,7 @@ int sam_get_version(bool info) { }; uint16_t payload_len = sizeof(payload); - sam_send_payload( + int exchange = sam_send_payload( 0x44, 0x0a, 0x44, payload, &payload_len, @@ -421,6 +467,22 @@ int sam_get_version(bool info) { &response_len ); + if (exchange != PM3_SUCCESS) { + res = exchange; + goto out; + } + + // The Artemis T=1 endpoint accepts the extended GetVersion warmup with a + // bare 9000 (unlike the T=0 endpoint, it does not return the version TLV). + // It is only a link-settling ping here, so a successful status is enough; + // the following InitAuth exchange performs the actual authentication. + if ((sc_active_device_cmd() == I2C_DEVICE_CMD_SEND_T1) && + (response_len >= 2) && + (response[response_len - 2] == 0x90) && + (response[response_len - 1] == 0x00)) { + goto out; + } + // resp: // c1 64 00 00 00 // bd 11 <- SAM response diff --git a/armsrc/sam_sc.c b/armsrc/sam_sc.c index d73cea345..5ea4f22ab 100644 --- a/armsrc/sam_sc.c +++ b/armsrc/sam_sc.c @@ -522,6 +522,9 @@ void sam_sc_handler(const PacketCommandNG *c) { s_sam_sc_session_active = false; I2C_Reset_EnterMainProgram(); StartTicks(); +#if SAM_SC_FORCE_T1_TA1_95 + sc_request_sam_t1_profile(); +#endif smart_card_atr_t card; if (GetATR(&card, false) == false) { res = PM3_ECARDEXCHANGE; diff --git a/client/resources/sim023.bin b/client/resources/sim023.bin new file mode 100644 index 0000000000000000000000000000000000000000..a49746fdfde3465ab1c4404d541f29604f20d1a2 GIT binary patch literal 7160 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