hf felica liteauth: add external authentication

This commit is contained in:
q0jt 2025-08-06 08:32:34 +09:00
commit 30689f7d3c
No known key found for this signature in database

View file

@ -33,9 +33,16 @@
#include "cliparser.h" // cliparser #include "cliparser.h" // cliparser
#include "util_posix.h" // msleep #include "util_posix.h" // msleep
#define FELICA_BLK_SIZE 16 #define FELICA_BLK_SIZE 16
#define FELICA_BLK_HALF (FELICA_BLK_SIZE/2) #define FELICA_BLK_HALF (FELICA_BLK_SIZE/2)
#define FELICA_BLK_NUMBER_RC 0x80
#define FELICA_BLK_NUMBER_ID 0x82
#define FELICA_BLK_NUMBER_WCNT 0x90
#define FELICA_BLK_NUMBER_MACA 0x91
#define FELICA_BLK_NUMBER_STATE 0x92
#define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001) #define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001)
#define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010) #define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010)
#define FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS (0b001000) #define FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS (0b001000)
@ -2289,7 +2296,7 @@ static uint16_t PrintFliteBlock(uint16_t tracepos, uint8_t *trace, uint16_t trac
} }
break; break;
case 0x90: { case 0x90: {
PrintAndLogEx(NORMAL, "Write count, RO: %02x %02x %02x ", trace[3], trace[4], trace[5]); PrintAndLogEx(NORMAL, "Write counter, RO: %02x %02x %02x ", trace[3], trace[4], trace[5]);
} }
break; break;
case 0x91: { case 0x91: {
@ -2545,7 +2552,32 @@ static int parse_multiple_block_data(const uint8_t *data, const size_t datalen,
memcpy(out, data + res_size, num * FELICA_BLK_SIZE); memcpy(out, data + res_size, num * FELICA_BLK_SIZE);
if (outlen) {
*outlen = num * FELICA_BLK_SIZE; *outlen = num * FELICA_BLK_SIZE;
}
return PM3_SUCCESS;
}
static int send_rd_multiple_plain(uint8_t flags, uint16_t datalen, uint8_t *data, uint8_t *out) {
clear_and_send_command(flags, datalen, data, false);
PacketResponseNG res;
if (waitCmdFelica(false, &res, false) == false) {
PrintAndLogEx(ERR, "\nGot no response from card");
return PM3_ERFTRANS;
}
uint8_t block_data[FELICA_BLK_SIZE*4];
memset(block_data, 0, sizeof(block_data));
uint8_t outlen = 0;
int ret = parse_multiple_block_data(res.data.asBytes, sizeof(res.data.asBytes), block_data, &outlen);
if (ret) {
return PM3_ERFTRANS;
}
memcpy(out, block_data, outlen);
return PM3_SUCCESS; return PM3_SUCCESS;
} }
@ -2598,12 +2630,22 @@ cleanup:
return ret; return ret;
} }
static void felica_auth_context_free(felica_auth_context_t *auth_ctx) {
if (!auth_ctx) {
return;
}
mbedtls_platform_zeroize(auth_ctx->session_key, sizeof(auth_ctx->session_key));
mbedtls_platform_zeroize(auth_ctx->random_challenge, sizeof(auth_ctx->random_challenge));
}
static int felica_generate_mac( static int felica_generate_mac(
mbedtls_des3_context *ctx, mbedtls_des3_context *ctx,
const felica_auth_context_t *auth_ctx, const felica_auth_context_t *auth_ctx,
const uint8_t *initialize_block, const uint8_t *initialize_block,
const uint8_t *block_data, const uint8_t *block_data,
const size_t length, const size_t length,
bool use_read_key,
uint8_t *mac) { uint8_t *mac) {
int ret = PM3_SUCCESS; int ret = PM3_SUCCESS;
@ -2619,7 +2661,16 @@ static int felica_generate_mac(
return PM3_EINVARG; return PM3_EINVARG;
} }
SwapEndian64ex(auth_ctx->session_key, sizeof(auth_ctx->session_key), 8, rev_sk); uint8_t sk[FELICA_BLK_SIZE];
if (use_read_key == false) {
memcpy(sk, auth_ctx->session_key + 8, 8);
memcpy(sk + 8, auth_ctx->session_key, 8);
} else {
memcpy(sk, auth_ctx->session_key, sizeof(auth_ctx->session_key));
}
SwapEndian64ex(sk, sizeof(sk), 8, rev_sk);
memcpy(iv, auth_ctx->random_challenge, sizeof(iv)); memcpy(iv, auth_ctx->random_challenge, sizeof(iv));
@ -2642,6 +2693,7 @@ static int felica_generate_mac(
SwapEndian64ex(out, FELICA_BLK_HALF, 8, mac); SwapEndian64ex(out, FELICA_BLK_HALF, 8, mac);
cleanup: cleanup:
mbedtls_platform_zeroize(sk, sizeof(sk));
mbedtls_platform_zeroize(rev_sk, sizeof(rev_sk)); mbedtls_platform_zeroize(rev_sk, sizeof(rev_sk));
mbedtls_platform_zeroize(iv, sizeof(iv)); mbedtls_platform_zeroize(iv, sizeof(iv));
mbedtls_platform_zeroize(out, sizeof(out)); mbedtls_platform_zeroize(out, sizeof(out));
@ -2650,79 +2702,58 @@ cleanup:
return ret; return ret;
} }
/** static int write_with_mac(
* Command parser for liteauth. mbedtls_des3_context *ctx,
* @param Cmd input data of the user. const felica_auth_context_t *auth_ctx,
* @return client result code. const uint8_t *counter,
*/ const uint8_t blk_number,
static int CmdHFFelicaAuthenticationLite(const char *Cmd) { const uint8_t *block_data,
CLIParserContext *ctx; uint8_t *out) {
CLIParserInit(&ctx, "hf felica liteauth",
"Authenticate",
"hf felica liteauth -i 11100910C11BC407\n"
"hf felica liteauth -k 46656c69436130313233343536616263\n"
"hf felica liteauth -c 701185c59f8d30afeab8e4b3a61f5cc4 -k 46656c69436130313233343536616263"
);
void *argtable[] = {
arg_param_begin,
arg_str0("k", "key", "<hex>", "set card key, 16 bytes"),
arg_str0("c", "", "<hex>", "set random challenge, 16 bytes"),
arg_str0("i", "", "<hex>", "set custom IDm"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint8_t key[FELICA_BLK_SIZE]; uint8_t initialize_blk[FELICA_BLK_HALF];
memset(key, 0, sizeof(key)); memset(initialize_blk, 0, sizeof(initialize_blk));
int keylen = 0;
int ret = CLIParamHexToBuf(arg_get_str(ctx, 1), key, sizeof(key), &keylen); uint8_t wcnt[3];
if (ret) { memcpy(wcnt, counter, 3);
CLIParserFree(ctx);
return PM3_EINVARG; memcpy(initialize_blk, wcnt, sizeof(wcnt));
initialize_blk[4] = blk_number;
initialize_blk[6] = 0x91;
uint8_t mac[FELICA_BLK_HALF];
int ret = felica_generate_mac(ctx, auth_ctx, initialize_blk, block_data, FELICA_BLK_SIZE, false, mac);
if (ret != PM3_SUCCESS) {
return ret;
} }
uint8_t rc[FELICA_BLK_SIZE]; uint8_t payload[FELICA_BLK_SIZE*2];
memset(rc, 0, sizeof(rc)); memset(payload, 0, sizeof(payload));
int rclen = 0;
ret = CLIParamHexToBuf(arg_get_str(ctx, 2), rc, sizeof(rc), &rclen);
if (ret) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
uint8_t idm[8]; memcpy(payload, block_data, FELICA_BLK_SIZE);
memset(idm, 0, sizeof(idm)); memcpy(payload + FELICA_BLK_SIZE, mac, sizeof(mac));
int ilen = 0; memcpy(payload + FELICA_BLK_SIZE + sizeof(mac), wcnt, sizeof(wcnt));
ret = CLIParamHexToBuf(arg_get_str(ctx, 3), idm, sizeof(idm), &ilen);
if (ret) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
CLIParserFree(ctx); memcpy(out, payload, sizeof(payload));
if (!ilen) { return PM3_SUCCESS;
if (last_known_card.IDm[0] != 0 && last_known_card.IDm[1] != 0) { }
memcpy(idm, last_known_card.IDm, sizeof(idm));
} else {
PrintAndLogEx(WARNING, "No last known card! Use `" _YELLOW_("hf felica reader") "` first or set a custom IDm");
return PM3_EINVARG;
}
}
PrintAndLogEx(INFO, "Card Key: %s", sprint_hex(key, sizeof(key))); static int felica_internal_authentication(
PrintAndLogEx(INFO, "Random Challenge(RC): %s", sprint_hex(rc, sizeof(rc))); const uint8_t *idm,
const uint8_t* rc,
PrintAndLogEx(SUCCESS, "FeliCa lite - auth started"); const size_t rclen,
mbedtls_des3_context *ctx,
const felica_auth_context_t *auth_ctx) {
uint8_t data[PM3_CMD_DATA_SIZE]; uint8_t data[PM3_CMD_DATA_SIZE];
memset(data, 0, sizeof(data)); memset(data, 0, sizeof(data));
uint8_t blk_numbers[1] = {0x80}; // RC uint8_t blk_numbers[1] = {FELICA_BLK_NUMBER_RC};
uint16_t datalen = 0; uint16_t datalen = 0;
ret = write_without_encryption(idm, (uint8_t)sizeof(blk_numbers), blk_numbers, rc, sizeof(rc), data, &datalen); int ret = write_without_encryption(idm, (uint8_t)sizeof(blk_numbers), blk_numbers, rc, rclen, data, &datalen);
if (ret) { if (ret) {
return PM3_ERFTRANS; return PM3_ERFTRANS;
} }
@ -2732,28 +2763,16 @@ static int CmdHFFelicaAuthenticationLite(const char *Cmd) {
uint8_t flags = (FELICA_APPEND_CRC | FELICA_RAW | FELICA_NO_DISCONNECT); uint8_t flags = (FELICA_APPEND_CRC | FELICA_RAW | FELICA_NO_DISCONNECT);
felica_status_response_t sres; felica_status_response_t res;
if (send_wr_plain(flags, datalen, data, false, &sres) != PM3_SUCCESS) { if (send_wr_plain(flags, datalen, data, false, &res) != PM3_SUCCESS) {
return PM3_ERFTRANS; return PM3_ERFTRANS;
} }
if (sres.status_flags.status_flag1[0] != 0x00 && sres.status_flags.status_flag2[0] != 0x00) { if (res.status_flags.status_flag1[0] != 0x00 && res.status_flags.status_flag2[0] != 0x00) {
PrintAndLogEx(ERR, "\nError RC Write"); PrintAndLogEx(ERR, "\nError RC Write");
return PM3_ERFTRANS; return PM3_ERFTRANS;
} }
mbedtls_des3_context des3_ctx;
mbedtls_des3_init(&des3_ctx);
felica_auth_context_t auth_ctx;
ret = felica_auth_context_init(&des3_ctx, rc, sizeof(rc), key, sizeof(key), &auth_ctx);
if (ret) {
return PM3_ERFTRANS;
}
PrintAndLogEx(INFO, "Session Key(SK): %s", sprint_hex(auth_ctx.session_key, sizeof(auth_ctx.session_key)));
memset(data, 0, sizeof(data)); memset(data, 0, sizeof(data));
uint8_t blk_numbers2[2] = {0x82, 0x91}; uint8_t blk_numbers2[2] = {0x82, 0x91};
@ -2766,20 +2785,10 @@ static int CmdHFFelicaAuthenticationLite(const char *Cmd) {
AddCrc(data, datalen); AddCrc(data, datalen);
datalen += 2; datalen += 2;
flags &= ~FELICA_NO_DISCONNECT; uint8_t pd[FELICA_BLK_SIZE*sizeof(blk_numbers2)];
clear_and_send_command(flags, datalen, data, false);
PacketResponseNG pres;
if (waitCmdFelica(false, &pres, false) == false) {
PrintAndLogEx(ERR, "\nGot no response from card");
return PM3_ERFTRANS;
}
uint8_t pd[64];
memset(pd, 0, sizeof(pd)); memset(pd, 0, sizeof(pd));
uint8_t pd_size = 0; ret = send_rd_multiple_plain(flags, datalen, data, pd);
ret = parse_multiple_block_data(pres.data.asBytes, sizeof(pres.data.asBytes), pd, &pd_size);
if (ret) { if (ret) {
return PM3_ERFTRANS; return PM3_ERFTRANS;
} }
@ -2793,32 +2802,200 @@ static int CmdHFFelicaAuthenticationLite(const char *Cmd) {
uint8_t initialize_blk[8]; uint8_t initialize_blk[8];
memset(initialize_blk, 0xFF, sizeof(initialize_blk)); memset(initialize_blk, 0xFF, sizeof(initialize_blk));
initialize_blk[0] = 0x82; // ID initialize_blk[0] = FELICA_BLK_NUMBER_ID;
initialize_blk[1] = 0x00; initialize_blk[1] = 0x00;
initialize_blk[2] = 0x91; // MAC_A initialize_blk[2] = FELICA_BLK_NUMBER_MACA;
initialize_blk[3] = 0x00; initialize_blk[3] = 0x00;
uint8_t mac[FELICA_BLK_HALF]; uint8_t mac[FELICA_BLK_HALF];
ret = felica_generate_mac(&des3_ctx, &auth_ctx, initialize_blk, id_blk, sizeof(id_blk), mac); ret = felica_generate_mac(ctx, auth_ctx, initialize_blk, id_blk, sizeof(id_blk), true, mac);
if (ret) { if (ret) {
return PM3_ERFTRANS; return PM3_ERFTRANS;
} }
PrintAndLogEx(SUCCESS, "MAC_A: %s", sprint_hex(mac, sizeof(mac))); PrintAndLogEx(SUCCESS, "MAC_A: %s", sprint_hex(mac, sizeof(mac)));
mbedtls_des3_free(&des3_ctx);
if (memcmp(mac_blk, mac, FELICA_BLK_HALF) != 0) { if (memcmp(mac_blk, mac, FELICA_BLK_HALF) != 0) {
PrintAndLogEx(ERR, "\nAuthenticate Failed"); PrintAndLogEx(ERR, "\nInternal Authenticate: " _RED_("Failed"));
return PM3_ERFTRANS; return PM3_ERFTRANS;
} }
PrintAndLogEx(SUCCESS, "Authenticate Success"); PrintAndLogEx(SUCCESS, "Internal Authenticate: " _GREEN_("OK"));
return PM3_SUCCESS; return PM3_SUCCESS;
} }
static int felica_external_authentication(
const uint8_t *idm,
mbedtls_des3_context *ctx,
const felica_auth_context_t *auth_ctx,
bool keep) {
uint8_t data[PM3_CMD_DATA_SIZE_MIX];
memset(data, 0, sizeof(data));
uint8_t flags = (FELICA_APPEND_CRC | FELICA_RAW | FELICA_NO_DISCONNECT);
uint16_t datalen = 0;
uint8_t blk_numbers[1] = {FELICA_BLK_NUMBER_WCNT};
int ret = read_without_encryption(idm, (uint8_t)sizeof(blk_numbers), blk_numbers, data, &datalen);
if (ret) {
return PM3_ERFTRANS;
}
AddCrc(data, datalen);
datalen += 2;
uint8_t wcnt_blk[FELICA_BLK_SIZE];
ret = send_rd_multiple_plain(flags, datalen, data, wcnt_blk);
if (ret) {
return PM3_ERFTRANS;
}
uint8_t ext_auth[FELICA_BLK_SIZE];
memset(ext_auth, 0, sizeof(ext_auth));
ext_auth[0] = 1; // After Authenticate
uint8_t mac_w[FELICA_BLK_SIZE*2];
ret = write_with_mac(ctx, auth_ctx, wcnt_blk, FELICA_BLK_NUMBER_STATE, ext_auth, mac_w);
if (ret) {
return PM3_ERFTRANS;
}
uint8_t blk_numbers2[2] = {FELICA_BLK_NUMBER_STATE, FELICA_BLK_NUMBER_MACA};
ret = write_without_encryption(idm, (uint8_t)sizeof(blk_numbers2), blk_numbers2, mac_w, sizeof(mac_w), data, &datalen);
if (ret) {
return PM3_ERFTRANS;
}
AddCrc(data, datalen);
datalen += 2;
if (keep == false) {
flags &= ~FELICA_NO_DISCONNECT;
}
felica_status_response_t res;
if (send_wr_plain(flags, datalen, data, false, &res) != PM3_SUCCESS) {
return PM3_ERFTRANS;
}
if (res.status_flags.status_flag1[0] != 0x00 && res.status_flags.status_flag2[0] != 0x00) {
PrintAndLogEx(ERR, "\nExternal Authenticate: " _RED_("Failed"));
return PM3_ERFTRANS;
}
PrintAndLogEx(SUCCESS, "External Authenticate: " _GREEN_("OK"));
return PM3_SUCCESS;
}
/**
* Command parser for liteauth.
* @param Cmd input data of the user.
* @return client result code.
*/
static int CmdHFFelicaAuthenticationLite(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf felica liteauth",
"Authenticate",
"hf felica liteauth -i 11100910C11BC407\n"
"hf felica liteauth -k 46656c69436130313233343536616263\n"
"hf felica liteauth -k 46656c69436130313233343536616263 -@\n"
"hf felica liteauth -c 701185c59f8d30afeab8e4b3a61f5cc4 -k 46656c69436130313233343536616263"
);
void *argtable[] = {
arg_param_begin,
arg_str0("k", "key", "<hex>", "set card key, 16 bytes"),
arg_str0("c", "", "<hex>", "set random challenge, 16 bytes"),
arg_str0("i", "", "<hex>", "set custom IDm"),
arg_lit0("@", "", "keep signal field ON after receive"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint8_t key[FELICA_BLK_SIZE];
memset(key, 0, sizeof(key));
int keylen = 0;
int res = CLIParamHexToBuf(arg_get_str(ctx, 1), key, sizeof(key), &keylen);
if (res) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
uint8_t rc[FELICA_BLK_SIZE];
memset(rc, 0, sizeof(rc));
int rclen = 0;
res = CLIParamHexToBuf(arg_get_str(ctx, 2), rc, sizeof(rc), &rclen);
if (res) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
uint8_t idm[8];
memset(idm, 0, sizeof(idm));
int ilen = 0;
res = CLIParamHexToBuf(arg_get_str(ctx, 3), idm, sizeof(idm), &ilen);
if (res) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
bool keep_field_on = arg_get_lit(ctx, 4);
CLIParserFree(ctx);
if (!ilen) {
if (last_known_card.IDm[0] != 0 && last_known_card.IDm[1] != 0) {
memcpy(idm, last_known_card.IDm, sizeof(idm));
} else {
PrintAndLogEx(WARNING, "No last known card! Use `" _YELLOW_("hf felica reader") "` first or set a custom IDm");
return PM3_EINVARG;
}
}
int ret = PM3_SUCCESS;
PrintAndLogEx(INFO, "Card Key: %s", sprint_hex(key, sizeof(key)));
PrintAndLogEx(INFO, "Random Challenge(RC): %s", sprint_hex(rc, sizeof(rc)));
PrintAndLogEx(SUCCESS, "FeliCa lite - auth started");
mbedtls_des3_context des3_ctx;
mbedtls_des3_init(&des3_ctx);
felica_auth_context_t auth_ctx;
ret = felica_auth_context_init(&des3_ctx, rc, sizeof(rc), key, sizeof(key), &auth_ctx);
if (ret) {
goto cleanup;
}
PrintAndLogEx(INFO, "Session Key(SK): %s", sprint_hex(auth_ctx.session_key, sizeof(auth_ctx.session_key)));
ret = felica_internal_authentication(idm, rc, sizeof(rc), &des3_ctx, &auth_ctx);
if (ret) {
goto cleanup;
}
ret = felica_external_authentication(idm, &des3_ctx, &auth_ctx, keep_field_on);
if (ret) {
goto cleanup;
}
cleanup:
mbedtls_des3_free(&des3_ctx);
felica_auth_context_free(&auth_ctx);
return ret;
}
static int CmdHFFelicaCmdRaw(const char *Cmd) { static int CmdHFFelicaCmdRaw(const char *Cmd) {
CLIParserContext *ctx; CLIParserContext *ctx;