Merge pull request #3289 from kormax/duox-maintenance

DUOX Maintenance
This commit is contained in:
Iceman
2026-05-05 18:21:47 +02:00
committed by GitHub
11 changed files with 333 additions and 506 deletions
@@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICKDCCAYmgAwIBAgIBAjAKBggqhkjOPQQDAjBGMQwwCgYDVQQKDANOWFAxHTAb
BgNVBAMMFE5YUCBMRUFGIFJvb3RDQXZFNTAwMRcwFQYDVQQFEw42MzcwOTMyMDEz
MDAwMDAgFw0yNTA2MzAxMzM0NDRaGA8yMDY1MDYzMDEzMzQ0NFowRjEMMAoGA1UE
CgwDTlhQMR0wGwYDVQQDDBROWFAgTEVBRiBSb290Q0F2RTIwMDEXMBUGA1UEBRMO
NjM3MDkzMjAxMzEwMDAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQtJ4G+QcIn
WKYTgQ9n7HjfEXbEdlshK0khjGxYQIpa2j2ZcyCdgiiROogWlzz+XJ6/2MZpdTLN
1bU+4TTS8Rs8o2YwZDASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUdktaJUHxC+YHsMDqW+Cu4yoZt94wHwYDVR0jBBgwFoAUJm5E
Yuw6nFX1IreUXpdhFNdmY5QwCgYIKoZIzj0EAwIDgYwAMIGIAkIA1PsrUHpbSGEH
ETjfYygzBoqLCS+Ed6jzfAYCsEjPAEdIN7v+bej86yKckN2+FSiG5/LJwJi5nTLg
fbRrFH+M+yACQgDH9nudmERpYiOiEHwWHZFHOHyXT8di6KEmPqR7WN2mSFAqYN2n
psI8ZlIxiHmLs2kEUqIR36XxLk4LBIiMInLvHw==
-----END CERTIFICATE-----
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICajCCAcygAwIBAgIBATAKBggqhkjOPQQDBDBGMQwwCgYDVQQKDANOWFAxHTAb
BgNVBAMMFE5YUCBMRUFGIFJvb3RDQXZFNTAwMRcwFQYDVQQFEw42MzcwOTMyMDEz
MDAwMDAgFw0yNTA2MzAxMzI3NDlaGA8yMDc1MDYzMDEzMjc0OVowRjEMMAoGA1UE
CgwDTlhQMR0wGwYDVQQDDBROWFAgTEVBRiBSb290Q0F2RTUwMDEXMBUGA1UEBRMO
NjM3MDkzMjAxMzAwMDAwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABAC3O2rxBVGV
IVtqIP43XLA7/Jg3vaYXZPBppyjHcdnn5zylnLP3B9igEld/SWCosMdrpKuqddps
EiTeIMJIUu9VRQDS6leU5itYBrAsHccq/KUpUDigSj6HI0WiVRzLpUyYXOUIYq+l
zytBQb0jXRXT4ddOyw5x0nL6wLjJADPZ+Qi8c6NmMGQwEgYDVR0TAQH/BAgwBgEB
/wIBATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFCZuRGLsOpxV9SK3lF6XYRTX
ZmOUMB8GA1UdIwQYMBaAFCZuRGLsOpxV9SK3lF6XYRTXZmOUMAoGCCqGSM49BAME
A4GLADCBhwJCAP0JGHZUujRaVqvLH3NZFxhxRGjAJ0c4Sg/NRMq2XQTrJmwZ6P/A
5yuscF/TtVk1GpqRuyMiNyCHZrzkR61/kh8JAkF1xB2/wdgpfdmnJMZQw0p4ofim
g8YJjVLRj3Ug5lPvWbG7TS7BGZcsLxPMZp2UvB1K+O1YqNuxLWxIm/xIPHWmjQ==
-----END CERTIFICATE-----
-223
View File
@@ -51,9 +51,6 @@
#include "mifare/prime.h"
#include "util.h"
#include "crypto/originality.h"
#include "x509_crt.h"
#include "mbedtls/oid.h"
#include "mbedtls/sha256.h"
#define MAX_KEY_LEN 24
#define MAX_KEYS_LIST_LEN 1024
@@ -84,8 +81,6 @@
// LEAF Verified Open Application
#define LEAF_VERIFIED_DEFAULT_AID 0xF51CD6U
#define LEAF_VERIFIED_CERT_FILE 0x02
#define LEAF_VERIFIED_MAX_CERT_LEN 4096
#define LEAF_COMMUNITY_ROOT_KEY_PATH "duox_trust/leaf_community/leaf_community-root-public-key.der"
#define DUOX_VDE_DEFAULT_AID 0x1010F6U
#define DUOX_VDE_CERT_FILE 0x00
@@ -8055,223 +8050,6 @@ static int CmdHF14ADesVdeSign(const char *Cmd) {
}
// Look up an attribute in a DN by OID. Returns pointer to mbedtls value buf or NULL.
static const mbedtls_x509_buf *leaf_dn_find_oid(const mbedtls_x509_name *dn, const char *oid_buf, size_t oid_len) {
while (dn != NULL) {
if (dn->oid.len == oid_len && memcmp(dn->oid.p, oid_buf, oid_len) == 0)
return &dn->val;
dn = dn->next;
}
return NULL;
}
static int CmdHF14ADesLeaf(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mfdes leaf",
"Read and verify a LEAF Verified credential on a MIFARE DUOX card.\n"
"Selects the LEAF Verified Open Application, reads the X.509 certificate\n"
"from file 0x02, verifies it was signed by the LEAF Root CA, performs ISO\n"
"Internal Authenticate, and verifies the card signature with the public key\n"
"embedded in the certificate.",
"hf mfdes leaf -> verify with default AID D61CF5\n"
"hf mfdes leaf -v -> verbose output\n"
"hf mfdes leaf --aid D61CF5 -> override AID\n"
"hf mfdes leaf -d 00112233445566778899AABBCCDDEEFF -> explicit 16-byte challenge\n");
void *argtable[] = {
arg_param_begin,
arg_lit0("a", "apdu", "Show APDU requests and responses"), // 1
arg_lit0("v", "verbose", "Verbose output"), // 2
arg_str0("d", "challenge", "<hex>", "Challenge / RndA (16 bytes, random if omitted)"), // 3
arg_str0(NULL, "aid", "<hex>", "Application ID (3 bytes, default D61CF5)"), // 4
arg_int0("n", "keynum", "<dec>", "Key number (P2, default 0)"), // 5
arg_str0(NULL, "isoid", "<hex>", "Application ISO ID / ISO DF FID (2 bytes)"), // 6
arg_str0(NULL, "dfname", "<hex>", "Application ISO DF Name (1-16 hex bytes)"), // 7
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool APDULogging = arg_get_lit(ctx, 1);
bool verbose = arg_get_lit(ctx, 2);
uint8_t challenge[DUOX_INTAUTH_CHALLENGE_LEN] = {0};
int challenge_len = 0;
CLIGetHexWithReturn(ctx, 3, challenge, &challenge_len);
bool challenge_provided = (challenge_len > 0);
if (challenge_provided && challenge_len != DUOX_INTAUTH_CHALLENGE_LEN) {
PrintAndLogEx(ERR, "Challenge must be exactly 16 bytes, got %d", challenge_len);
CLIParserFree(ctx);
return PM3_EINVARG;
}
int keynum = arg_get_int_def(ctx, 5, 0);
if (keynum < 0 || keynum > 255) {
PrintAndLogEx(ERR, "Key number must be 0..255");
CLIParserFree(ctx);
return PM3_EINVARG;
}
mfd_app_select app_select = MfdSelectionInitAID(LEAF_VERIFIED_DEFAULT_AID);
if (MfdSelectionApplyCmdParameters(ctx, 4, 6, 7, &app_select) != PM3_SUCCESS) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
SetAPDULogging(APDULogging);
CLIParserFree(ctx);
uint8_t leaf_root_pubkey[65] = {0};
int pk_res = ensure_ec_public_key(LEAF_COMMUNITY_ROOT_KEY_PATH, MBEDTLS_ECP_DP_SECP256R1, leaf_root_pubkey, sizeof(leaf_root_pubkey));
if (pk_res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to load LEAF Root CA public key from " _YELLOW_("%s") " (%d)", LEAF_COMMUNITY_ROOT_KEY_PATH, pk_res);
return pk_res;
}
if (!challenge_provided) {
int res = pcrypto_rng_fill_oneshot(challenge, sizeof(challenge), "hf_mfdes_leaf");
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to generate random challenge");
return res;
}
}
PrintAndLogEx(INFO, "--- " _CYAN_("LEAF Verified Credential Check"));
if (verbose) {
MfdSelectionPrint(&app_select);
}
// Step 1: Select application
DesfireContext_t dctx = {0};
dctx.commMode = DCMPlain;
dctx.cmdSet = DCCNativeISO;
if (MfdSelectionSelectApplication(&dctx, &app_select, verbose) != PM3_SUCCESS) {
DropField();
return PM3_ESOFT;
}
// Step 2: Read X.509 certificate from file 0x02 (length=0 reads to EOF)
uint8_t cert_buf[LEAF_VERIFIED_MAX_CERT_LEN] = {0};
size_t cert_len = 0;
int res = DesfireReadFile(&dctx, LEAF_VERIFIED_CERT_FILE, 0, 0, cert_buf, &cert_len);
if (res != PM3_SUCCESS || cert_len == 0) {
PrintAndLogEx(ERR, "Read certificate file 0x%02X " _RED_("failed") " (%d)", LEAF_VERIFIED_CERT_FILE, res);
DropField();
return PM3_ESOFT;
}
PrintAndLogEx(SUCCESS, "Certificate read " _GREEN_("ok") " (%zu bytes)", cert_len);
if (verbose)
print_hex_break(cert_buf, cert_len, 32);
// Step 3: Parse certificate
mbedtls_x509_crt cert;
mbedtls_x509_crt_init(&cert);
int xres = mbedtls_x509_crt_parse_der(&cert, cert_buf, cert_len);
if (xres != 0) {
PrintAndLogEx(ERR, "X.509 parse " _RED_("failed") " (-0x%04x)", -xres);
mbedtls_x509_crt_free(&cert);
DropField();
return PM3_ESOFT;
}
// Print certificate details
PrintAndLogEx(INFO, "--- " _CYAN_("Certificate"));
char dnbuf[256] = {0};
mbedtls_x509_dn_gets(dnbuf, sizeof(dnbuf), &cert.subject);
PrintAndLogEx(INFO, "Subject...... " _YELLOW_("%s"), dnbuf);
mbedtls_x509_dn_gets(dnbuf, sizeof(dnbuf), &cert.issuer);
PrintAndLogEx(INFO, "Issuer....... " _YELLOW_("%s"), dnbuf);
char idbuf[128] = {0};
const mbedtls_x509_buf *open_id = leaf_dn_find_oid(&cert.subject,
MBEDTLS_OID_AT_SERIAL_NUMBER,
MBEDTLS_OID_SIZE(MBEDTLS_OID_AT_SERIAL_NUMBER));
if (open_id != NULL && open_id->len > 0) {
size_t cp = (open_id->len < sizeof(idbuf) - 1) ? open_id->len : sizeof(idbuf) - 1;
memcpy(idbuf, open_id->p, cp);
PrintAndLogEx(INFO, "Open ID...... " _YELLOW_("%s"), idbuf);
}
PrintAndLogEx(INFO, "Valid from... " _YELLOW_("%04d-%02d-%02d %02d:%02d:%02d"),
cert.valid_from.year, cert.valid_from.mon, cert.valid_from.day,
cert.valid_from.hour, cert.valid_from.min, cert.valid_from.sec);
PrintAndLogEx(INFO, "Valid to..... " _YELLOW_("%04d-%02d-%02d %02d:%02d:%02d"),
cert.valid_to.year, cert.valid_to.mon, cert.valid_to.day,
cert.valid_to.hour, cert.valid_to.min, cert.valid_to.sec);
if (cert.serial.len > 0)
PrintAndLogEx(INFO, "Serial....... " _YELLOW_("%s"), sprint_hex_inrow(cert.serial.p, cert.serial.len));
uint8_t fp[32] = {0};
if (mbedtls_sha256_ret(cert_buf, cert_len, fp, 0) == 0)
PrintAndLogEx(INFO, "SHA-256...... " _YELLOW_("%s"), sprint_hex_inrow(fp, sizeof(fp)));
// Step 4: Verify certificate signature against LEAF Root CA public key.
// The certificate uses ECDSA-SHA256 over secp256r1; ecdsa_signature_verify
// accepts the DER-encoded signature stored in cert.sig.
PrintAndLogEx(INFO, "--- " _CYAN_("Root CA Verification"));
bool root_ok = false;
int rres = ecdsa_signature_verify(
MBEDTLS_ECP_DP_SECP256R1,
leaf_root_pubkey,
cert.tbs.p,
(int)cert.tbs.len,
cert.sig.p,
cert.sig.len,
true);
if (rres == PM3_SUCCESS) {
PrintAndLogEx(SUCCESS, "Root signature " _GREEN_("verified") " (LEAF Root CA P-256)");
root_ok = true;
} else {
PrintAndLogEx(ERR, "Root signature " _RED_("verification failed") " (%d)", rres);
}
// Extract card public key (P-256, uncompressed)
uint8_t card_pubkey[65] = {0};
int kres = ecdsa_public_key_from_pk(&cert.pk, MBEDTLS_ECP_DP_SECP256R1, card_pubkey, sizeof(card_pubkey));
mbedtls_x509_crt_free(&cert);
if (kres != 0) {
PrintAndLogEx(ERR, "Failed to extract card public key (-0x%04x)", -kres);
DropField();
return PM3_ESOFT;
}
if (verbose)
PrintAndLogEx(INFO, "Card pubkey.. %s", sprint_hex_inrow(card_pubkey, sizeof(card_pubkey)));
// Step 5: ISO Internal Authenticate
PrintAndLogEx(INFO, "--- " _CYAN_("ISO Internal Authenticate"));
PrintAndLogEx(INFO, "Challenge.... " _YELLOW_("%s"), sprint_hex_inrow(challenge, sizeof(challenge)));
uint8_t card_random[DUOX_INTAUTH_CHALLENGE_LEN] = {0};
uint8_t signature_rs[DUOX_INTAUTH_SIG_LEN] = {0};
res = duox_intauth_exchange(APDULogging, verbose, (uint8_t)keynum, challenge, card_random, signature_rs);
DropField();
if (res != PM3_SUCCESS)
return res;
// Step 6: Verify card signature with extracted public key.
PrintAndLogEx(INFO, "--- " _CYAN_("Signature Verification"));
bool card_ok = false;
int sig_res = duox_intauth_verify_sig(verbose, card_pubkey, challenge, card_random, signature_rs);
if (sig_res == PM3_SUCCESS) {
PrintAndLogEx(SUCCESS, "Card signature " _GREEN_("verified"));
card_ok = true;
} else {
PrintAndLogEx(ERR, "Card signature " _RED_("verification failed"));
}
PrintAndLogEx(NORMAL, "");
if (root_ok && card_ok) {
PrintAndLogEx(SUCCESS, "LEAF Verified credential " _GREEN_("AUTHENTIC"));
if (idbuf[0] != '\0')
PrintAndLogEx(SUCCESS, "Open ID...... " _GREEN_("%s"), idbuf);
} else {
PrintAndLogEx(ERR, "LEAF Verified credential " _RED_("FAILED") " (root=%s, card=%s)",
root_ok ? "ok" : "fail", card_ok ? "ok" : "fail");
}
return (root_ok && card_ok) ? PM3_SUCCESS : PM3_ESOFT;
}
static const CLIParserOption mfdesValidateMethodOpts[] = {
{MFDES_VALIDATE_METHOD_AUTO, "auto"},
{MFDES_VALIDATE_METHOD_INTAUTH, "intauth"},
@@ -9329,7 +9107,6 @@ static command_t CommandTable[] = {
{"verifycert", CmdHF14ADesVerifyCert, IfPm3Iso14443a, "Validate cert from file and verify key possession"},
{"intauth", CmdHF14ADesIntAuth, IfPm3Iso14443a, "ISO Internal Authenticate (ECDSA challenge-response)"},
{"vdesign", CmdHF14ADesVdeSign, IfPm3Iso14443a, "VDE ECDSASign (EV charging signature over 32-byte challenge)"},
{"leaf", CmdHF14ADesLeaf, IfPm3Iso14443a, "LEAF Verified credential read + cert + auth check"},
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("System") " -----------------------"},
{"test", CmdHF14ADesTest, AlwaysAvailable, "Regression crypto tests"},
{NULL, NULL, NULL, NULL}
+12 -195
View File
@@ -18,12 +18,9 @@
#include "crypto/duoxcrypto.h"
#include <ctype.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <mbedtls/asn1.h>
#include <mbedtls/ecdsa.h>
#include <mbedtls/ecp.h>
@@ -40,6 +37,7 @@
#include "x509_crt.h"
#define DUOX_CERTIFICATE_ANCHOR_INPUT_LEN 8192
#define DUOX_CERTIFICATE_ANCHOR_SEARCH_DEPTH 4
static int duox_cert_info_from_x509_crt(const mbedtls_x509_crt *cert, duox_cert_info_t *out);
@@ -55,189 +53,6 @@ const char *duox_certificate_format_name(duox_certificate_format_t format) {
}
}
const char *duox_cert_info_format_name(const duox_cert_info_t *cert) {
if (cert == NULL) {
return duox_certificate_format_name(DUOX_CERTIFICATE_FORMAT_UNKNOWN);
}
return duox_certificate_format_name(cert->format);
}
static void duox_trim_ascii_inplace(char *text) {
if (text == NULL) {
return;
}
size_t start = 0;
size_t len = strlen(text);
while (start < len && isspace((unsigned char)text[start])) {
start++;
}
while (len > start && isspace((unsigned char)text[len - 1])) {
len--;
}
if (start > 0) {
memmove(text, text + start, len - start);
}
text[len - start] = '\0';
}
static int duox_copy_without_whitespace(const char *src, char *dst, size_t dst_size, size_t *dst_len) {
if (src == NULL || dst == NULL || dst_len == NULL || dst_size == 0) {
return PM3_EINVARG;
}
size_t out = 0;
for (size_t i = 0; src[i] != '\0'; i++) {
if (isspace((unsigned char)src[i])) {
continue;
}
if ((out + 1) >= dst_size) {
return PM3_EOVFLOW;
}
dst[out++] = src[i];
}
dst[out] = '\0';
*dst_len = out;
return PM3_SUCCESS;
}
static bool duox_path_is_directory(const char *path) {
if (path == NULL) {
return false;
}
struct stat st;
if (stat(path, &st) != 0) {
return false;
}
return S_ISDIR(st.st_mode) != 0;
}
static bool duox_path_is_regular_file(const char *path) {
if (path == NULL) {
return false;
}
struct stat st;
if (stat(path, &st) != 0) {
return false;
}
return S_ISREG(st.st_mode) != 0;
}
static const char *duox_path_basename(const char *path) {
if (path == NULL) {
return "";
}
const char *base = strrchr(path, '/');
const char *base_win = strrchr(path, '\\');
if (base == NULL || (base_win != NULL && base_win > base)) {
base = base_win;
}
return (base == NULL) ? path : (base + 1);
}
static void duox_path_basename_without_ext(const char *path, char *out, size_t out_len) {
if (out == NULL || out_len == 0) {
return;
}
out[0] = '\0';
const char *base = duox_path_basename(path);
if (base[0] == '\0') {
return;
}
snprintf(out, out_len, "%s", base);
char *dot = strrchr(out, '.');
if (dot != NULL && dot != out) {
*dot = '\0';
}
}
static int duox_qsort_path_cmp(const void *a, const void *b) {
const char *pa = (const char *)a;
const char *pb = (const char *)b;
return strcmp(pa, pb);
}
static int duox_collect_certificate_anchor_paths_recursive(const char *dirpath,
char paths[][DUOX_CERTIFICATE_ANCHOR_PATH_LEN],
size_t max_paths, size_t *count) {
if (dirpath == NULL || paths == NULL || count == NULL) {
return PM3_EINVARG;
}
DIR *dir = opendir(dirpath);
if (dir == NULL) {
return PM3_EFILE;
}
struct dirent *entry = NULL;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || entry->d_name[0] == '.') {
continue;
}
char fullpath[DUOX_CERTIFICATE_ANCHOR_PATH_LEN] = {0};
if (snprintf(fullpath, sizeof(fullpath), "%s/%s", dirpath, entry->d_name) >= (int)sizeof(fullpath)) {
continue;
}
if (duox_path_is_directory(fullpath)) {
int res = duox_collect_certificate_anchor_paths_recursive(fullpath, paths, max_paths, count);
if (res != PM3_SUCCESS) {
closedir(dir);
return res;
}
continue;
}
if (!duox_path_is_regular_file(fullpath)) {
continue;
}
if (*count >= max_paths) {
closedir(dir);
return PM3_EOVFLOW;
}
snprintf(paths[*count], DUOX_CERTIFICATE_ANCHOR_PATH_LEN, "%s", fullpath);
(*count)++;
}
closedir(dir);
return PM3_SUCCESS;
}
static int duox_collect_certificate_anchor_paths(const char *anchor_store_dir,
char paths[][DUOX_CERTIFICATE_ANCHOR_PATH_LEN],
size_t max_paths, size_t *count) {
if (anchor_store_dir == NULL || anchor_store_dir[0] == '\0' || paths == NULL || count == NULL) {
return PM3_EINVARG;
}
char *rootdir = NULL;
int res = searchFile(&rootdir, RESOURCES_SUBDIR, anchor_store_dir, "", true);
if (res != PM3_SUCCESS) {
return res;
}
if (!duox_path_is_directory(rootdir)) {
free(rootdir);
return PM3_EFILE;
}
*count = 0;
res = duox_collect_certificate_anchor_paths_recursive(rootdir, paths, max_paths, count);
free(rootdir);
if (res != PM3_SUCCESS) {
return res;
}
qsort(paths, *count, sizeof(paths[0]), duox_qsort_path_cmp);
return PM3_SUCCESS;
}
int duox_certificate_anchor_public_key(const duox_certificate_anchor_t *anchor,
mbedtls_ecp_group_id *curveid,
const uint8_t **pubkey, size_t *pubkey_len) {
@@ -299,7 +114,7 @@ static int duox_load_x509_certificate_input(const char *input, mbedtls_x509_crt
return PM3_EOVFLOW;
}
memcpy(normalized, input, input_len + 1);
duox_trim_ascii_inplace(normalized);
str_trim_ascii_inplace(normalized);
if (normalized[0] == '\0') {
return PM3_EINVARG;
}
@@ -319,7 +134,7 @@ static int duox_load_x509_certificate_input(const char *input, mbedtls_x509_crt
char compact[DUOX_CERTIFICATE_ANCHOR_INPUT_LEN] = {0};
size_t compact_len = 0;
if (duox_copy_without_whitespace(normalized, compact, sizeof(compact), &compact_len) != PM3_SUCCESS || compact_len == 0) {
if (str_copy_without_whitespace(normalized, compact, sizeof(compact), &compact_len) != PM3_SUCCESS || compact_len == 0) {
return PM3_EINVARG;
}
@@ -425,7 +240,7 @@ static int duox_load_certificate_anchor_from_file_path(const char *filepath, duo
}
char filename_anchor_name[DUOX_CERTIFICATE_ANCHOR_NAME_LEN] = {0};
duox_path_basename_without_ext(filepath, filename_anchor_name, sizeof(filename_anchor_name));
path_basename_without_ext(filepath, filename_anchor_name, sizeof(filename_anchor_name));
if (filename_anchor_name[0] == '\0') {
return PM3_EINVARG;
}
@@ -446,14 +261,15 @@ static int duox_load_named_certificate_anchor_from_store(const char *token, cons
char paths[DUOX_CERTIFICATE_ANCHOR_MAX_PATHS][DUOX_CERTIFICATE_ANCHOR_PATH_LEN] = {{0}};
size_t path_count = 0;
int res = duox_collect_certificate_anchor_paths(anchor_store_dir, paths, ARRAYLEN(paths), &path_count);
int res = collect_resource_file_paths(anchor_store_dir, (char *)paths, sizeof(paths[0]), ARRAYLEN(paths), &path_count,
false, DUOX_CERTIFICATE_ANCHOR_SEARCH_DEPTH);
if (res != PM3_SUCCESS) {
return res;
}
for (size_t i = 0; i < path_count; i++) {
char filename_anchor_name[DUOX_CERTIFICATE_ANCHOR_NAME_LEN] = {0};
duox_path_basename_without_ext(paths[i], filename_anchor_name, sizeof(filename_anchor_name));
path_basename_without_ext(paths[i], filename_anchor_name, sizeof(filename_anchor_name));
if (filename_anchor_name[0] == '\0') {
continue;
}
@@ -466,7 +282,7 @@ static int duox_load_named_certificate_anchor_from_store(const char *token, cons
const char *matched_path = NULL;
for (size_t i = 0; i < path_count; i++) {
char filename_anchor_name[DUOX_CERTIFICATE_ANCHOR_NAME_LEN] = {0};
duox_path_basename_without_ext(paths[i], filename_anchor_name, sizeof(filename_anchor_name));
path_basename_without_ext(paths[i], filename_anchor_name, sizeof(filename_anchor_name));
if (filename_anchor_name[0] == '\0' || !str_startswith_case_insensitive(filename_anchor_name, token)) {
continue;
}
@@ -491,7 +307,7 @@ int duox_load_certificate_anchor_from_input(const char *input, const char *ancho
char normalized[DUOX_CERTIFICATE_ANCHOR_INPUT_LEN] = {0};
snprintf(normalized, sizeof(normalized), "%s", input);
duox_trim_ascii_inplace(normalized);
str_trim_ascii_inplace(normalized);
if (normalized[0] == '\0') {
return PM3_EINVARG;
}
@@ -499,7 +315,7 @@ int duox_load_certificate_anchor_from_input(const char *input, const char *ancho
char *resolved_path = NULL;
if (searchFile(&resolved_path, RESOURCES_SUBDIR, normalized, "", true) == PM3_SUCCESS) {
int res = PM3_EINVARG;
if (duox_path_is_regular_file(resolved_path)) {
if (path_is_regular_file(resolved_path)) {
res = duox_load_certificate_anchor_from_file_path(resolved_path, anchor);
}
free(resolved_path);
@@ -532,7 +348,8 @@ int duox_load_certificate_anchors_from_store(const char *anchor_store_dir,
char paths[DUOX_CERTIFICATE_ANCHOR_MAX_PATHS][DUOX_CERTIFICATE_ANCHOR_PATH_LEN] = {{0}};
size_t path_count = 0;
int res = duox_collect_certificate_anchor_paths(anchor_store_dir, paths, ARRAYLEN(paths), &path_count);
int res = collect_resource_file_paths(anchor_store_dir, (char *)paths, sizeof(paths[0]), ARRAYLEN(paths), &path_count,
false, DUOX_CERTIFICATE_ANCHOR_SEARCH_DEPTH);
if (res != PM3_SUCCESS) {
return res;
}
-1
View File
@@ -76,7 +76,6 @@ typedef struct {
} duox_certificate_anchor_t;
const char *duox_certificate_format_name(duox_certificate_format_t format);
const char *duox_cert_info_format_name(const duox_cert_info_t *cert);
int duox_certificate_anchor_public_key(const duox_certificate_anchor_t *anchor,
mbedtls_ecp_group_id *curveid,
const uint8_t **pubkey, size_t *pubkey_len);
+8 -81
View File
@@ -22,7 +22,6 @@
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <mbedtls/asn1.h>
#include <mbedtls/des.h>
#include <mbedtls/aes.h>
@@ -106,78 +105,6 @@ int pcrypto_rng_fill_oneshot(uint8_t *out, size_t out_len, const char *personali
return res;
}
static void pcrypto_trim_ascii_inplace(char *text) {
if (text == NULL) {
return;
}
size_t start = 0;
size_t len = strlen(text);
while (start < len && isspace((unsigned char)text[start])) {
start++;
}
while (len > start && isspace((unsigned char)text[len - 1])) {
len--;
}
if (start > 0) {
memmove(text, text + start, len - start);
}
text[len - start] = '\0';
}
static void pcrypto_unescape_newlines_inplace(char *text) {
if (text == NULL) {
return;
}
size_t read_pos = 0;
size_t write_pos = 0;
size_t len = strlen(text);
while (read_pos < len) {
if (text[read_pos] == '\\' && (read_pos + 1) < len) {
char esc = text[read_pos + 1];
if (esc == 'n') {
text[write_pos++] = '\n';
read_pos += 2;
continue;
}
if (esc == 'r') {
text[write_pos++] = '\r';
read_pos += 2;
continue;
}
if (esc == 't') {
text[write_pos++] = '\t';
read_pos += 2;
continue;
}
}
text[write_pos++] = text[read_pos++];
}
text[write_pos] = '\0';
}
static int pcrypto_copy_without_whitespace(const char *src, char *dst, size_t dst_size, size_t *dst_len) {
if (src == NULL || dst == NULL || dst_len == NULL || dst_size == 0) {
return PM3_EINVARG;
}
size_t out = 0;
for (size_t i = 0; src[i] != '\0'; i++) {
if (isspace((unsigned char)src[i])) {
continue;
}
if ((out + 1) >= dst_size) {
return PM3_EOVFLOW;
}
dst[out++] = src[i];
}
dst[out] = '\0';
*dst_len = out;
return PM3_SUCCESS;
}
static int pcrypto_extract_priv_scalar_from_pk(const mbedtls_pk_context *pkctx,
mbedtls_ecp_group_id curveid,
uint8_t *out_priv, size_t out_priv_len) {
@@ -265,7 +192,7 @@ static int pcrypto_parse_ec_private_base64(const char *input,
char compact[PCRYPTO_MAX_KEY_INPUT] = {0};
size_t compact_len = 0;
int res = pcrypto_copy_without_whitespace(input, compact, sizeof(compact), &compact_len);
int res = str_copy_without_whitespace(input, compact, sizeof(compact), &compact_len);
if (res != PM3_SUCCESS || compact_len == 0) {
return PM3_EINVARG;
}
@@ -399,7 +326,7 @@ static int pcrypto_parse_ec_private_text(const char *input, bool allow_file_path
return PM3_EOVFLOW;
}
memcpy(normalized, input, input_len + 1);
pcrypto_trim_ascii_inplace(normalized);
str_trim_ascii_inplace(normalized);
if (normalized[0] == '\0') {
return PM3_EINVARG;
@@ -416,13 +343,13 @@ static int pcrypto_parse_ec_private_text(const char *input, bool allow_file_path
// Only unescape after path resolution fails, to avoid mutating valid paths
// (for example Windows paths containing '\t', '\n' or '\r').
pcrypto_unescape_newlines_inplace(normalized);
str_unescape_newlines_inplace(normalized);
uint8_t decoded[PCRYPTO_MAX_KEY_INPUT] = {0};
int decoded_len = -1;
char compact[PCRYPTO_MAX_KEY_INPUT] = {0};
size_t compact_len = 0;
if (pcrypto_copy_without_whitespace(normalized, compact, sizeof(compact), &compact_len) == PM3_SUCCESS &&
if (str_copy_without_whitespace(normalized, compact, sizeof(compact), &compact_len) == PM3_SUCCESS &&
compact_len > 0) {
decoded_len = hex_to_bytes(compact, decoded, sizeof(decoded));
}
@@ -740,7 +667,7 @@ static int pcrypto_parse_ec_public_base64(const char *input,
char compact[PCRYPTO_MAX_KEY_INPUT] = {0};
size_t compact_len = 0;
int res = pcrypto_copy_without_whitespace(input, compact, sizeof(compact), &compact_len);
int res = str_copy_without_whitespace(input, compact, sizeof(compact), &compact_len);
if (res != PM3_SUCCESS || compact_len == 0) {
return PM3_EINVARG;
}
@@ -777,7 +704,7 @@ static int pcrypto_parse_ec_public_text(const char *input, bool allow_file_path,
return PM3_EOVFLOW;
}
memcpy(normalized, input, input_len + 1);
pcrypto_trim_ascii_inplace(normalized);
str_trim_ascii_inplace(normalized);
if (normalized[0] == '\0') {
return PM3_EINVARG;
@@ -793,14 +720,14 @@ static int pcrypto_parse_ec_public_text(const char *input, bool allow_file_path,
}
// Only unescape after path resolution fails
pcrypto_unescape_newlines_inplace(normalized);
str_unescape_newlines_inplace(normalized);
// Try as hex string
uint8_t decoded[PCRYPTO_MAX_KEY_INPUT] = {0};
int decoded_len = -1;
char compact[PCRYPTO_MAX_KEY_INPUT] = {0};
size_t compact_len = 0;
if (pcrypto_copy_without_whitespace(normalized, compact, sizeof(compact), &compact_len) == PM3_SUCCESS &&
if (str_copy_without_whitespace(normalized, compact, sizeof(compact), &compact_len) == PM3_SUCCESS &&
compact_len > 0) {
decoded_len = hex_to_bytes(compact, decoded, sizeof(decoded));
}
+161 -6
View File
@@ -127,20 +127,176 @@ int fileExists(const char *filename) {
* @param filename
* @return
*/
static bool is_directory(const char *filename) {
bool path_is_directory(const char *path) {
if (path == NULL) {
return false;
}
#ifdef _WIN32
struct _stat st;
if (_stat(filename, &st) == -1)
if (_stat(path, &st) == -1)
return false;
#else
struct stat st;
// stat(filename, &st);
if (lstat(filename, &st) == -1)
if (lstat(path, &st) == -1)
return false;
#endif
return S_ISDIR(st.st_mode) != 0;
}
bool path_is_regular_file(const char *path) {
if (path == NULL) {
return false;
}
#ifdef _WIN32
struct _stat st;
if (_stat(path, &st) == -1)
return false;
#else
struct stat st;
if (stat(path, &st) == -1)
return false;
#endif
return S_ISREG(st.st_mode) != 0;
}
const char *path_basename(const char *path) {
if (path == NULL) {
return "";
}
const char *base = strrchr(path, '/');
const char *base_win = strrchr(path, '\\');
if (base == NULL || (base_win != NULL && base_win > base)) {
base = base_win;
}
return (base == NULL) ? path : (base + 1);
}
void path_basename_without_ext(const char *path, char *out, size_t out_len) {
if (out == NULL || out_len == 0) {
return;
}
out[0] = '\0';
const char *base = path_basename(path);
if (base[0] == '\0') {
return;
}
snprintf(out, out_len, "%s", base);
char *dot = strrchr(out, '.');
if (dot != NULL && dot != out) {
*dot = '\0';
}
}
static int qsort_path_cmp(const void *a, const void *b) {
const char *pa = (const char *)a;
const char *pb = (const char *)b;
return strcmp(pa, pb);
}
static char *path_list_slot(char *paths, size_t path_len, size_t index) {
return paths + (index * path_len);
}
int collect_file_paths_recursive(const char *dirpath, char *paths, size_t path_len,
size_t max_paths, size_t *count, bool include_hidden, size_t max_depth) {
if (dirpath == NULL || paths == NULL || path_len == 0 || count == NULL) {
return PM3_EINVARG;
}
DIR *dir = opendir(dirpath);
if (dir == NULL) {
return PM3_EFILE;
}
struct dirent *entry = NULL;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 ||
(include_hidden == false && entry->d_name[0] == '.')) {
continue;
}
char *fullpath = calloc(path_len, sizeof(char));
if (fullpath == NULL) {
closedir(dir);
return PM3_EMALLOC;
}
const char *sep = "";
size_t dir_len = strlen(dirpath);
if (dir_len > 0 && dirpath[dir_len - 1] != '/' && dirpath[dir_len - 1] != '\\') {
sep = PATHSEP;
}
if (snprintf(fullpath, path_len, "%s%s%s", dirpath, sep, entry->d_name) >= (int)path_len) {
free(fullpath);
continue;
}
if (path_is_directory(fullpath)) {
if (max_depth == 0) {
free(fullpath);
continue;
}
int res = collect_file_paths_recursive(fullpath, paths, path_len, max_paths, count, include_hidden, max_depth - 1);
free(fullpath);
if (res != PM3_SUCCESS) {
closedir(dir);
return res;
}
continue;
}
if (!path_is_regular_file(fullpath)) {
free(fullpath);
continue;
}
if (*count >= max_paths) {
free(fullpath);
closedir(dir);
return PM3_EOVFLOW;
}
if (snprintf(path_list_slot(paths, path_len, *count), path_len, "%s", fullpath) >= (int)path_len) {
free(fullpath);
continue;
}
free(fullpath);
(*count)++;
}
closedir(dir);
return PM3_SUCCESS;
}
int collect_resource_file_paths(const char *resource_dir, char *paths, size_t path_len,
size_t max_paths, size_t *count, bool include_hidden, size_t max_depth) {
if (resource_dir == NULL || resource_dir[0] == '\0' || paths == NULL || path_len == 0 || count == NULL) {
return PM3_EINVARG;
}
char *rootdir = NULL;
int res = searchFile(&rootdir, RESOURCES_SUBDIR, resource_dir, "", true);
if (res != PM3_SUCCESS) {
return res;
}
if (!path_is_directory(rootdir)) {
free(rootdir);
return PM3_EFILE;
}
*count = 0;
res = collect_file_paths_recursive(rootdir, paths, path_len, max_paths, count, include_hidden, max_depth);
free(rootdir);
if (res != PM3_SUCCESS) {
return res;
}
qsort(paths, *count, path_len, qsort_path_cmp);
return PM3_SUCCESS;
}
bool setDefaultPath(savePaths_t pathIndex, const char *path) {
if (pathIndex < spItemCount) {
@@ -2884,7 +3040,7 @@ static int filelist(const char *path, const char *ext, uint8_t last, bool tentat
tmp_fullpath[1023] = 0x00;
strncat(tmp_fullpath, namelist[i]->d_name, strlen(tmp_fullpath) - 1);
if (is_directory(tmp_fullpath)) {
if (path_is_directory(tmp_fullpath)) {
char newpath[1024];
if (strcmp(namelist[i]->d_name, ".") == 0 || strcmp(namelist[i]->d_name, "..") == 0)
@@ -3144,7 +3300,7 @@ int searchFile(char **foundpath, const char *pm3dir, const char *searchname, con
return PM3_EINVARG;
}
if (is_directory(searchname)) {
if (path_is_directory(searchname)) {
return PM3_EINVARG;
}
@@ -3397,4 +3553,3 @@ int pm3_save_fm11rf08s_nonces(const char *fn, iso14a_fm11rf08s_nonces_with_data_
}
return PM3_SUCCESS;
}
+36
View File
@@ -114,6 +114,42 @@ typedef enum {
int fileExists(const char *filename);
/**
* @brief Check whether a path exists and is a directory.
*/
bool path_is_directory(const char *path);
/**
* @brief Check whether a path exists and is a regular file.
*/
bool path_is_regular_file(const char *path);
/**
* @brief Return the final path component, or an empty string for NULL input.
*/
const char *path_basename(const char *path);
/**
* @brief Copy the final path component without its last file extension.
*/
void path_basename_without_ext(const char *path, char *out, size_t out_len);
/**
* @brief Recursively collect regular file paths under a directory into fixed-size slots.
*
* A max_depth of 0 scans only dirpath and does not descend into subdirectories.
*/
int collect_file_paths_recursive(const char *dirpath, char *paths, size_t path_len,
size_t max_paths, size_t *count, bool include_hidden, size_t max_depth);
/**
* @brief Resolve a resources subdirectory and recursively collect regular file paths from it.
*
* A max_depth of 0 scans only the resolved resource_dir and does not descend into subdirectories.
*/
int collect_resource_file_paths(const char *resource_dir, char *paths, size_t path_len,
size_t max_paths, size_t *count, bool include_hidden, size_t max_depth);
// set a path in the path list g_session.defaultPaths
bool setDefaultPath(savePaths_t pathIndex, const char *path);
+72
View File
@@ -1944,3 +1944,75 @@ size_t unduplicate(uint8_t *d, size_t n, const uint8_t item_n) {
return write_index;
}
void str_trim_ascii_inplace(char *s) {
if (s == NULL) {
return;
}
size_t start = 0;
size_t len = strlen(s);
while (start < len && isspace((unsigned char)s[start])) {
start++;
}
while (len > start && isspace((unsigned char)s[len - 1])) {
len--;
}
if (start > 0) {
memmove(s, s + start, len - start);
}
s[len - start] = '\0';
}
void str_unescape_newlines_inplace(char *s) {
if (s == NULL) {
return;
}
size_t read_pos = 0;
size_t write_pos = 0;
size_t len = strlen(s);
while (read_pos < len) {
if (s[read_pos] == '\\' && (read_pos + 1) < len) {
char esc = s[read_pos + 1];
if (esc == 'n') {
s[write_pos++] = '\n';
read_pos += 2;
continue;
}
if (esc == 'r') {
s[write_pos++] = '\r';
read_pos += 2;
continue;
}
if (esc == 't') {
s[write_pos++] = '\t';
read_pos += 2;
continue;
}
}
s[write_pos++] = s[read_pos++];
}
s[write_pos] = '\0';
}
int str_copy_without_whitespace(const char *src, char *dst, size_t dst_size, size_t *dst_len) {
if (src == NULL || dst == NULL || dst_len == NULL || dst_size == 0) {
return PM3_EINVARG;
}
size_t out = 0;
for (size_t i = 0; src[i] != '\0'; i++) {
if (isspace((unsigned char)src[i])) {
continue;
}
if ((out + 1) >= dst_size) {
return PM3_EOVFLOW;
}
dst[out++] = src[i];
}
dst[out] = '\0';
*dst_len = out;
return PM3_SUCCESS;
}
+15
View File
@@ -215,4 +215,19 @@ uint8_t get_highest_frequency(const uint8_t *d, uint8_t n);
size_t unduplicate(uint8_t *d, size_t n, const uint8_t item_n);
/**
* @brief Trim leading and trailing ASCII whitespace from a mutable string.
*/
void str_trim_ascii_inplace(char *s);
/**
* @brief Replace escaped \n, \r, and \t sequences with their control characters in-place.
*/
void str_unescape_newlines_inplace(char *s);
/**
* @brief Copy a string while dropping all ASCII whitespace characters.
*/
int str_copy_without_whitespace(const char *src, char *dst, size_t dst_size, size_t *dst_len);
#endif