hf secc crypto bug fixes

This commit is contained in:
Antiklesys
2026-04-12 03:16:41 +08:00
parent 84bc068652
commit d331e57c78
4 changed files with 104 additions and 11 deletions
+63 -10
View File
@@ -52,6 +52,14 @@ static hid_apdu_entry_t s_apdu_table[HID_APDU_MAX_ENTRIES];
static uint8_t s_apdu_count = 0;
static uint8_t s_scp02_key[16] = {0};
// Per-card key diversification data emitted in INITIALIZE UPDATE and used to
// derive card-specific static ENC/MAC/DEK keys via VISA-2. All-zero disables
// diversification (s_scp02_key is used directly as the static base key).
static uint8_t s_kdd[10] = {0};
// Key Version Number emitted in the INIT UPDATE response (GP KVN).
static uint8_t s_kvn = 0x01;
// Default response for unmatched APDUs (loaded from JSON "DefaultResponse").
// When s_default_resp_len == 0 the handler falls back to the legacy 90 00 reply.
static uint8_t s_default_resp[HID_APDU_MAX_RESP] = {0};
@@ -82,8 +90,10 @@ static void hid_config_card_set_default_resp(const uint8_t *resp, uint8_t len) {
memcpy(s_default_resp, resp, s_default_resp_len);
}
static void hid_config_card_set_scp02_key(const uint8_t *key) {
static void hid_config_card_set_scp02_key(const uint8_t *key, const uint8_t *kdd, uint8_t kvn) {
memcpy(s_scp02_key, key, 16);
memcpy(s_kdd, kdd, 10);
s_kvn = kvn;
s_seq_counter = 0;
}
@@ -91,13 +101,50 @@ static void hid_config_card_set_scp02_key(const uint8_t *key) {
// Internal crypto helpers
// ---------------------------------------------------------------------------
// Derive the per-card static base key for the given SCP02 key type:
// type=0x01 -> K_ENC, 0x02 -> K_MAC, 0x03 -> K_DEK
// VISA-2 diversification block (NIST SP800-108 style, but fixed JCOP layout):
// d = KDD[0:2] || KDD[4:8] || F0 || type || KDD[0:2] || KDD[4:8] || 0F || type
// The diversified key is 3DES-ECB( master, d ) over the two 8-byte halves.
// When s_kdd is all zero we return s_scp02_key directly (legacy "no
// diversification" mode - works with test cards that share a common master).
static void scp02_get_base_key(uint8_t type, uint8_t *out16) {
bool kdd_zero = true;
for (int i = 0; i < 10; i++) {
if (s_kdd[i]) { kdd_zero = false; break; }
}
if (kdd_zero) {
memcpy(out16, s_scp02_key, 16);
return;
}
uint8_t block[16];
memcpy(block, s_kdd, 2);
memcpy(block + 2, s_kdd + 4, 4);
block[6] = 0xF0;
block[7] = type;
memcpy(block + 8, s_kdd, 2);
memcpy(block + 10, s_kdd + 4, 4);
block[14] = 0x0F;
block[15] = type;
mbedtls_des3_context ctx;
mbedtls_des3_init(&ctx);
mbedtls_des3_set2key_enc(&ctx, s_scp02_key);
mbedtls_des3_crypt_ecb(&ctx, block, out16);
mbedtls_des3_crypt_ecb(&ctx, block + 8, out16 + 8);
mbedtls_des3_free(&ctx);
}
// Derive a 16-byte SCP02 session key using 3DES-CBC with null IV.
// constant0/constant1 select the key type (0x01,0x82=S-ENC; 0x01,0x01=S-MAC).
static void derive_scp02_session_key(uint8_t c0, uint8_t c1, uint16_t sc, uint8_t *out16) {
// base_key16 is the diversified per-card static key (from scp02_get_base_key).
// constant0/constant1 select the key type (0x01,0x82=S-ENC; 0x01,0x01=S-MAC;
// 0x01,0x81=DEK). The session counter is from the current INIT UPDATE.
static void derive_scp02_session_key(const uint8_t *base_key16, uint8_t c0, uint8_t c1, uint16_t sc, uint8_t *out16) {
uint8_t deriv[16] = {c0, c1, (uint8_t)(sc >> 8), (uint8_t)(sc & 0xFF),
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t iv[8] = {0};
tdes_nxp_send(deriv, out16, 16, s_scp02_key, iv, 2);
tdes_nxp_send(deriv, out16, 16, base_key16, iv, 2);
}
// Retail MAC (ISO 9797-1 Algorithm 3): single-DES for all-but-last blocks,
@@ -148,8 +195,10 @@ static void scp02_full_3des_cbc_mac(const uint8_t *key16, const uint8_t *data, s
// Card cryptogram = full-3DES-CBC-MAC(S-ENC, HC(8) || SC(2)||CC(6) || 80 00*7)
static void compute_card_cryptogram(const uint8_t *host_challenge, uint8_t *out) {
uint8_t k_enc[16];
scp02_get_base_key(0x01, k_enc);
uint8_t s_enc[16];
derive_scp02_session_key(0x01, 0x82, s_seq_counter, s_enc);
derive_scp02_session_key(k_enc, 0x01, 0x82, s_seq_counter, s_enc);
uint8_t data[24];
memcpy(data, host_challenge, 8);
@@ -251,8 +300,8 @@ bool hid_config_card_handle_iblock(const uint8_t *cmd, int len, tag_response_inf
uint8_t cryptogram[8];
compute_card_cryptogram(s_host_challenge, cryptogram);
memset(rsp, 0x00, 10); // key diversification data
rsp[10] = 0xFF; // key version (JCOP factory default)
memcpy(rsp, s_kdd, 10); // key diversification data
rsp[10] = s_kvn; // key version number
rsp[11] = 0x02; // SCP02
rsp[12] = (uint8_t)(s_seq_counter >> 8); // SC high
rsp[13] = (uint8_t)(s_seq_counter & 0xFF); // SC low
@@ -271,9 +320,13 @@ bool hid_config_card_handle_iblock(const uint8_t *cmd, int len, tag_response_inf
const uint8_t *host_crypto = &cmd[off + 5];
const uint8_t *cmac_recv = &cmd[off + 13];
uint8_t k_enc[16], k_mac[16];
scp02_get_base_key(0x01, k_enc);
scp02_get_base_key(0x02, k_mac);
uint8_t s_enc[16], s_mac[16];
derive_scp02_session_key(0x01, 0x82, s_seq_counter, s_enc);
derive_scp02_session_key(0x01, 0x01, s_seq_counter, s_mac);
derive_scp02_session_key(k_enc, 0x01, 0x82, s_seq_counter, s_enc);
derive_scp02_session_key(k_mac, 0x01, 0x01, s_seq_counter, s_mac);
uint8_t host_crypto_exp[8];
compute_host_cryptogram(s_enc, host_crypto_exp);
@@ -437,7 +490,7 @@ int hid_config_card_iso14_apdu(uint8_t *cmd, uint16_t cmd_len, bool send_chainin
void SimulateHIDConfigCard(const hid_sim_payload_t *payload) {
hid_config_card_set_apdu_table(payload->apdu_table, payload->apdu_count);
hid_config_card_set_default_resp(payload->default_resp, payload->default_resp_len);
hid_config_card_set_scp02_key(payload->scp02_key);
hid_config_card_set_scp02_key(payload->scp02_key, payload->kdd, payload->kvn ? payload->kvn : 0x01);
// Command buffers
uint8_t receivedCmd[MAX_FRAME_SIZE];
+5
View File
@@ -58,6 +58,11 @@ typedef struct {
uint8_t atqa[2]; // ATQA override (big-endian: [0]=high, [1]=low)
uint8_t sak; // SAK override
uint8_t scp02_key[16]; // SCP02 master key (from JSON "SCP02Key")
uint8_t kdd[10]; // 10-byte Key Diversification Data returned in INITIALIZE UPDATE.
// All-zero = legacy "no diversification" mode (scp02_key used directly).
// Any non-zero value enables VISA-2 diversification of scp02_key per
// SCP02 type (0x01=ENC, 0x02=MAC) on every handshake.
uint8_t kvn; // Key Version Number emitted in INIT UPDATE response (JSON "KVN", default 0x01).
uint8_t ats[20]; // ATS bytes without CRC (from JSON "ATS")
uint8_t ats_len; // actual number of valid bytes in ats[]
uint8_t default_resp[HID_APDU_MAX_RESP]; // fallback reply for unmatched APDUs (from JSON "DefaultResponse")
+4
View File
@@ -1,7 +1,11 @@
{
"_comment_KDD": "Optional 10-byte Key Diversification Data echoed in INITIALIZE UPDATE. When present and non-zero, the SCP02Key above is treated as the static MASTER GP key and diversified per-card via VISA-2 (separate K_ENC/K_MAC). All-zero or absent = no diversification (SCP02Key used directly).",
"_comment_KVN": "Optional 1-byte Key Version Number reported in INITIALIZE UPDATE. Default 0x01 matches the GP factory key set on most JCOP cards. Use 0xFF for an uninitialized JCOP factory test card.",
"UID": "00000000",
"AID": "A0000003820013000101",
"SCP02Key": "404142434445464748494A4B4C4D4E4F",
"KDD": "00000000000000000000",
"KVN": "01",
"ATS": "1478F7B10280590180415254454346477300011B",
"DefaultResponse": "6A82",
"APDUResponses": [
+32 -1
View File
@@ -40,6 +40,8 @@ typedef struct {
uint8_t atqa[2]; // big-endian: [0]=high byte, [1]=low byte
uint8_t sak;
uint8_t scp02_key[16]; // SCP02 master key (from JSON "SCP02Key")
uint8_t kdd[10]; // 10-byte Key Diversification Data (from JSON "KDD"); all-zero = no diversification
uint8_t kvn; // Key Version Number (from JSON "KVN", default 0x01)
uint8_t ats[20]; // ATS bytes without CRC (from JSON "ATS")
uint8_t ats_len; // actual number of valid bytes in ats[]
uint8_t default_resp[HID_APDU_MAX_RESP]; // fallback reply for unmatched APDUs (from JSON "DefaultResponse")
@@ -298,7 +300,7 @@ static int CmdHFHIDConfigSim(const char *Cmd) {
void *argtable[] = {
arg_param_begin,
arg_str1("f", "file", "<fn>", "JSON file with UID, AID, SCP02Key (without .json extension)"),
arg_str1("f", "file", "<fn>", "JSON file with UID, AID, SCP02Key, optional KDD/KVN (no .json ext)"),
arg_int0("n", "num", "<dec>", "Exit after <n> reader interactions. 0 = infinite"),
arg_param_end
};
@@ -345,6 +347,33 @@ static int CmdHFHIDConfigSim(const char *Cmd) {
return PM3_EINVARG;
}
// Parse optional KDD (10 bytes). If present, ARM diversifies SCP02Key
// per-card via VISA-2 on each handshake. If absent, all-zero is sent and
// ARM uses SCP02Key directly (legacy "no diversification" mode).
uint8_t kdd[10] = {0};
if (json_object_get(root, "KDD") != NULL) {
size_t kdd_len = 0;
if (JsonLoadBufAsHex(root, "$.KDD", kdd, sizeof(kdd), &kdd_len) != 0 || kdd_len != 10) {
PrintAndLogEx(ERR, "JSON 'KDD' field invalid (must be 10 bytes)");
json_decref(root);
return PM3_EINVARG;
}
}
// Parse optional KVN (Key Version Number, 1 byte). Default 0x01 matches
// the GP factory key set on most JCOP-based config cards.
uint8_t kvn = 0x01;
if (json_object_get(root, "KVN") != NULL) {
uint8_t kvn_buf[1] = {0};
size_t kvn_len = 0;
if (JsonLoadBufAsHex(root, "$.KVN", kvn_buf, sizeof(kvn_buf), &kvn_len) != 0 || kvn_len != 1) {
PrintAndLogEx(ERR, "JSON 'KVN' field invalid (must be 1 byte)");
json_decref(root);
return PM3_EINVARG;
}
kvn = kvn_buf[0];
}
// Parse ATS (1-20 bytes, without CRC)
uint8_t ats[20] = {0};
size_t ats_len_sz = 0;
@@ -465,10 +494,12 @@ static int CmdHFHIDConfigSim(const char *Cmd) {
payload.atqa[1] = 0x00; // HID Config Card ATQA low byte
payload.sak = 0x38; // HID Config Card SAK
payload.ats_len = (uint8_t)ats_len;
payload.kvn = kvn;
payload.default_resp_len = has_default_resp ? (uint8_t)default_resp_len_sz : 0;
payload.apdu_count = apdu_count;
memcpy(payload.uid, uid, uidlen);
memcpy(payload.scp02_key, scp02_key, sizeof(scp02_key));
memcpy(payload.kdd, kdd, sizeof(kdd));
memcpy(payload.ats, ats, ats_len);
if (has_default_resp)
memcpy(payload.default_resp, default_resp, default_resp_len_sz);