mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-12 11:18:11 -07:00
Binary file not shown.
@@ -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-----
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user