mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-11 21:14:43 +00:00
hf secc crypto bug fixes
This commit is contained in:
+63
-10
@@ -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];
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user