From 30689f7d3cc457df3230133c760871f2472ce82e Mon Sep 17 00:00:00 2001 From: q0jt <89930816+q0jt@users.noreply.github.com> Date: Wed, 6 Aug 2025 08:32:34 +0900 Subject: [PATCH] hf felica liteauth: add external authentication --- client/src/cmdhffelica.c | 371 +++++++++++++++++++++++++++++---------- 1 file changed, 274 insertions(+), 97 deletions(-) diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index c84c8c519..dd0b5789e 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -33,9 +33,16 @@ #include "cliparser.h" // cliparser #include "util_posix.h" // msleep + #define FELICA_BLK_SIZE 16 #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_READ_ONLY (0b000010) #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; 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; 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); - *outlen = num * FELICA_BLK_SIZE; + if (outlen) { + *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; } @@ -2598,12 +2630,22 @@ cleanup: 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( mbedtls_des3_context *ctx, const felica_auth_context_t *auth_ctx, const uint8_t *initialize_block, const uint8_t *block_data, const size_t length, + bool use_read_key, uint8_t *mac) { int ret = PM3_SUCCESS; @@ -2619,7 +2661,16 @@ static int felica_generate_mac( 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)); @@ -2642,6 +2693,7 @@ static int felica_generate_mac( SwapEndian64ex(out, FELICA_BLK_HALF, 8, mac); cleanup: + mbedtls_platform_zeroize(sk, sizeof(sk)); mbedtls_platform_zeroize(rev_sk, sizeof(rev_sk)); mbedtls_platform_zeroize(iv, sizeof(iv)); mbedtls_platform_zeroize(out, sizeof(out)); @@ -2650,79 +2702,58 @@ cleanup: return ret; } -/** - * 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 -c 701185c59f8d30afeab8e4b3a61f5cc4 -k 46656c69436130313233343536616263" - ); - void *argtable[] = { - arg_param_begin, - arg_str0("k", "key", "", "set card key, 16 bytes"), - arg_str0("c", "", "", "set random challenge, 16 bytes"), - arg_str0("i", "", "", "set custom IDm"), - arg_param_end - }; - CLIExecWithReturn(ctx, Cmd, argtable, true); +static int write_with_mac( + mbedtls_des3_context *ctx, + const felica_auth_context_t *auth_ctx, + const uint8_t *counter, + const uint8_t blk_number, + const uint8_t *block_data, + uint8_t *out) { + + uint8_t initialize_blk[FELICA_BLK_HALF]; + memset(initialize_blk, 0, sizeof(initialize_blk)); + + uint8_t wcnt[3]; + memcpy(wcnt, counter, 3); + + memcpy(initialize_blk, wcnt, sizeof(wcnt)); + initialize_blk[4] = blk_number; + initialize_blk[6] = 0x91; - uint8_t key[FELICA_BLK_SIZE]; - memset(key, 0, sizeof(key)); - int keylen = 0; - int ret = CLIParamHexToBuf(arg_get_str(ctx, 1), key, sizeof(key), &keylen); - if (ret) { - CLIParserFree(ctx); - return PM3_EINVARG; + 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]; - memset(rc, 0, sizeof(rc)); - int rclen = 0; - ret = CLIParamHexToBuf(arg_get_str(ctx, 2), rc, sizeof(rc), &rclen); - if (ret) { - CLIParserFree(ctx); - return PM3_EINVARG; - } + uint8_t payload[FELICA_BLK_SIZE*2]; + memset(payload, 0, sizeof(payload)); - uint8_t idm[8]; - memset(idm, 0, sizeof(idm)); - int ilen = 0; - ret = CLIParamHexToBuf(arg_get_str(ctx, 3), idm, sizeof(idm), &ilen); - if (ret) { - CLIParserFree(ctx); - return PM3_EINVARG; - } + memcpy(payload, block_data, FELICA_BLK_SIZE); + memcpy(payload + FELICA_BLK_SIZE, mac, sizeof(mac)); + memcpy(payload + FELICA_BLK_SIZE + sizeof(mac), wcnt, sizeof(wcnt)); - CLIParserFree(ctx); + memcpy(out, payload, sizeof(payload)); - 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; - } - } + return 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"); +static int felica_internal_authentication( + const uint8_t *idm, + const uint8_t* rc, + const size_t rclen, + mbedtls_des3_context *ctx, + const felica_auth_context_t *auth_ctx) { uint8_t data[PM3_CMD_DATA_SIZE]; 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; - 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) { return PM3_ERFTRANS; } @@ -2732,28 +2763,16 @@ static int CmdHFFelicaAuthenticationLite(const char *Cmd) { uint8_t flags = (FELICA_APPEND_CRC | FELICA_RAW | FELICA_NO_DISCONNECT); - felica_status_response_t sres; - if (send_wr_plain(flags, datalen, data, false, &sres) != PM3_SUCCESS) { - return PM3_ERFTRANS; + felica_status_response_t res; + if (send_wr_plain(flags, datalen, data, false, &res) != PM3_SUCCESS) { + 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"); 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)); uint8_t blk_numbers2[2] = {0x82, 0x91}; @@ -2766,20 +2785,10 @@ static int CmdHFFelicaAuthenticationLite(const char *Cmd) { AddCrc(data, datalen); datalen += 2; - flags &= ~FELICA_NO_DISCONNECT; - - 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]; + uint8_t pd[FELICA_BLK_SIZE*sizeof(blk_numbers2)]; memset(pd, 0, sizeof(pd)); - uint8_t pd_size = 0; - ret = parse_multiple_block_data(pres.data.asBytes, sizeof(pres.data.asBytes), pd, &pd_size); + ret = send_rd_multiple_plain(flags, datalen, data, pd); if (ret) { return PM3_ERFTRANS; } @@ -2793,32 +2802,200 @@ static int CmdHFFelicaAuthenticationLite(const char *Cmd) { uint8_t initialize_blk[8]; 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[2] = 0x91; // MAC_A + initialize_blk[2] = FELICA_BLK_NUMBER_MACA; initialize_blk[3] = 0x00; 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) { return PM3_ERFTRANS; } PrintAndLogEx(SUCCESS, "MAC_A: %s", sprint_hex(mac, sizeof(mac))); - mbedtls_des3_free(&des3_ctx); - if (memcmp(mac_blk, mac, FELICA_BLK_HALF) != 0) { - PrintAndLogEx(ERR, "\nAuthenticate Failed"); + PrintAndLogEx(ERR, "\nInternal Authenticate: " _RED_("Failed")); + return PM3_ERFTRANS; + } + + PrintAndLogEx(SUCCESS, "Internal Authenticate: " _GREEN_("OK")); + + 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; } - PrintAndLogEx(SUCCESS, "Authenticate Success"); + 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", "", "set card key, 16 bytes"), + arg_str0("c", "", "", "set random challenge, 16 bytes"), + arg_str0("i", "", "", "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) { CLIParserContext *ctx;