Implement 'hf mfd verifycert' command

This commit is contained in:
kormax
2026-05-04 22:07:33 +03:00
parent 2bf48a02c8
commit fa414f3b65
10 changed files with 2417 additions and 13 deletions
+1
View File
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
## [unreleased][unreleased]
- Added `hf mfd verifycert` command (@kormax)
- Added `hf calypso dump` command (@kormax)
- Added `pm3trace_edit.py` script for editing of pm3 trace files (@iceman1001)
- Added `hf calypso info` command (@kormax)
+1
View File
@@ -313,6 +313,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/common/hitag2/hitag2_crypto.c
${PM3_ROOT}/client/src/crypto/asn1dump.c
${PM3_ROOT}/client/src/crypto/asn1utils.c
${PM3_ROOT}/client/src/crypto/duoxcrypto.c
${PM3_ROOT}/client/src/crypto/libpcrypto.c
${PM3_ROOT}/client/src/crypto/originality.c
${PM3_ROOT}/client/src/emv/test/cda_test.c
+1
View File
@@ -770,6 +770,7 @@ SRCS = mifare/aiddesfire.c \
comms.c \
crypto/asn1dump.c \
crypto/asn1utils.c\
crypto/duoxcrypto.c\
crypto/libpcrypto.c\
crypto/originality.c\
emv/cmdemv.c \
+1
View File
@@ -233,6 +233,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/common/hitag2/hitag2_crypto.c
${PM3_ROOT}/client/src/crypto/asn1dump.c
${PM3_ROOT}/client/src/crypto/asn1utils.c
${PM3_ROOT}/client/src/crypto/duoxcrypto.c
${PM3_ROOT}/client/src/crypto/libpcrypto.c
${PM3_ROOT}/client/src/crypto/originality.c
${PM3_ROOT}/client/src/emv/test/cda_test.c
+1086 -13
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+105
View File
@@ -0,0 +1,105 @@
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// MIFARE DUOX certificate crypto helpers
//-----------------------------------------------------------------------------
#ifndef DUOXCRYPTO_H
#define DUOXCRYPTO_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <mbedtls/ecp.h>
#define DUOX_DEFAULT_CA_DIR "duox_trust"
#define DUOX_EC_PUBKEY_MAX_LEN 133
#define DUOX_MAX_CERTIFICATE_ANCHORS 16
#define DUOX_CERTIFICATE_ANCHOR_NAME_LEN 96
#define DUOX_CERTIFICATE_ANCHOR_MAX_PATHS 128
#define DUOX_CERTIFICATE_ANCHOR_PATH_LEN 1024
#define DUOX_CERT_TEXT_LEN 256
#define DUOX_VDE_CHALLENGE_LEN 32
#define DUOX_VDE_SIG_LEN 64
typedef enum {
DUOX_CERTIFICATE_FORMAT_UNKNOWN = 0,
DUOX_CERTIFICATE_FORMAT_X509,
DUOX_CERTIFICATE_FORMAT_GP_VDE,
} duox_certificate_format_t;
typedef struct {
duox_certificate_format_t format;
mbedtls_ecp_group_id curveid;
uint8_t pubkey[DUOX_EC_PUBKEY_MAX_LEN];
size_t pubkey_len;
char issuer[DUOX_CERT_TEXT_LEN];
char subject[DUOX_CERT_TEXT_LEN];
char serial[DUOX_CERT_TEXT_LEN];
char valid_from[DUOX_CERT_TEXT_LEN];
char valid_to[DUOX_CERT_TEXT_LEN];
char certificate_profile_note[DUOX_CERT_TEXT_LEN];
} duox_cert_info_t;
typedef struct {
mbedtls_ecp_group_id curveid;
uint8_t pubkey[DUOX_EC_PUBKEY_MAX_LEN];
size_t pubkey_len;
} duox_ec_public_key_t;
typedef enum {
DUOX_CERTIFICATE_ANCHOR_MATERIAL_NONE = 0,
DUOX_CERTIFICATE_ANCHOR_MATERIAL_PUBLIC_KEY,
DUOX_CERTIFICATE_ANCHOR_MATERIAL_CERT,
} duox_certificate_anchor_material_type_t;
typedef struct {
char name[DUOX_CERTIFICATE_ANCHOR_NAME_LEN];
char source[DUOX_CERTIFICATE_ANCHOR_PATH_LEN];
duox_certificate_anchor_material_type_t type;
union {
duox_ec_public_key_t key;
duox_cert_info_t cert;
} material;
} 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);
const char *duox_certificate_anchor_subject(const duox_certificate_anchor_t *anchor);
const char *duox_certificate_anchor_display_name(const duox_certificate_anchor_t *anchor);
int duox_load_certificate_anchor_from_input(const char *input, const char *anchor_store_dir,
duox_certificate_anchor_t *anchor);
int duox_load_certificate_anchors_from_store(const char *anchor_store_dir,
duox_certificate_anchor_t *anchors,
size_t max_anchors, size_t *out_count);
int duox_parse_x509_certificate(const uint8_t *data, size_t data_len,
bool verbose, duox_cert_info_t *out);
int duox_verify_x509_certificate_with_anchors(const uint8_t *data, size_t data_len,
const duox_certificate_anchor_t *anchors, size_t anchor_count,
bool verbose, duox_cert_info_t *out,
size_t *matched_index);
int duox_parse_gp_vde_certificate(const uint8_t *data, size_t data_len,
const duox_certificate_anchor_t *ca_anchors, size_t ca_anchor_count,
bool verify_signature,
bool verbose, duox_cert_info_t *out, size_t *matched_index);
int duox_parse_or_verify_certificate_variants(const uint8_t *data, size_t data_len,
const duox_certificate_anchor_t *ca_anchors, size_t ca_anchor_count,
bool verify_signature,
bool verbose, duox_cert_info_t *out, size_t *matched_index);
#endif /* DUOXCRYPTO_H */
+1
View File
@@ -526,6 +526,7 @@ const static vocabulary_t vocabulary[] = {
{ 0, "hf mfdes write" },
{ 0, "hf mfdes value" },
{ 0, "hf mfdes clearrecfile" },
{ 0, "hf mfdes verifycert" },
{ 0, "hf mfdes intauth" },
{ 0, "hf mfdes vdesign" },
{ 1, "hf mfdes test" },
+87
View File
@@ -1391,6 +1391,71 @@ void strn_upper(char *s, size_t n) {
s[i] = toupper(s[i]);
}
}
static int char_compare_case_insensitive(char a, char b) {
return tolower((unsigned char)a) - tolower((unsigned char)b);
}
bool str_equal_case_insensitive(const char *a, const char *b) {
if (a == NULL || b == NULL) {
return false;
}
while (*a != '\0' && *b != '\0') {
if (char_compare_case_insensitive(*a, *b) != 0) {
return false;
}
a++;
b++;
}
return *a == '\0' && *b == '\0';
}
bool str_startswith_case_insensitive(const char *s, const char *pre) {
if (s == NULL || pre == NULL) {
return false;
}
while (*pre != '\0') {
if (*s == '\0' || char_compare_case_insensitive(*s, *pre) != 0) {
return false;
}
s++;
pre++;
}
return true;
}
bool str_contains_case_insensitive(const char *s, const char *needle) {
if (s == NULL || needle == NULL) {
return false;
}
size_t needle_len = strlen(needle);
if (needle_len == 0) {
return true;
}
size_t s_len = strlen(s);
if (needle_len > s_len) {
return false;
}
for (size_t i = 0; i <= (s_len - needle_len); i++) {
size_t j = 0;
while (j < needle_len && char_compare_case_insensitive(s[i + j], needle[j]) == 0) {
j++;
}
if (j == needle_len) {
return true;
}
}
return false;
}
// check for prefix in string
bool str_startswith(const char *s, const char *pre) {
return strncmp(pre, s, strlen(pre)) == 0;
@@ -1499,6 +1564,28 @@ size_t str_nlen(const char *src, size_t maxlen) {
return len;
}
size_t str_copy(char *dst, size_t dst_size, const char *src) {
if (src == NULL) {
if (dst != NULL && dst_size > 0) {
dst[0] = '\0';
}
return 0;
}
size_t src_len = strlen(src);
if (dst == NULL || dst_size == 0) {
return src_len;
}
size_t copy_len = src_len;
if (copy_len >= dst_size) {
copy_len = dst_size - 1;
}
memcpy(dst, src, copy_len);
dst[copy_len] = '\0';
return src_len;
}
static bool str_regex_atom_matches(char atom, bool escaped, char c) {
if (!escaped && atom == '.') {
return true;
+4
View File
@@ -166,6 +166,9 @@ void str_lower(char *s); // converts string to lower case
void str_upper(char *s); // converts string to UPPER case
void strn_upper(char *s, size_t n);
bool str_equal_case_insensitive(const char *a, const char *b);
bool str_startswith_case_insensitive(const char *s, const char *pre);
bool str_contains_case_insensitive(const char *s, const char *needle);
bool str_startswith(const char *s, const char *pre); // check for prefix in string
bool str_endswith(const char *s, const char *suffix); // check for suffix in string
void clean_ascii(unsigned char *buf, size_t len);
@@ -181,6 +184,7 @@ void str_trim(char *s);
char *str_dup(const char *src);
char *str_ndup(const char *src, size_t len);
size_t str_nlen(const char *src, size_t maxlen);
size_t str_copy(char *dst, size_t dst_size, const char *src);
// Lightweight regex subset:
// - supported metacharacters: '^' (start), '$' (end), '.' (any char), '*' (zero or more)
// - escaping: '\\' to match the following char literally