29#include "esp_random.h"
36static const char*
TAG =
"GPG";
41 {
"mod_gpg.title",
"GPG"},
42 {
"mod_gpg.status",
"Status"},
43 {
"mod_gpg.generate",
"Generate Keys"},
44 {
"mod_gpg.export",
"Export Public"},
45 {
"mod_gpg.reset",
"Reset"},
46 {
"mod_gpg.settings",
"Settings"},
47 {
"mod_gpg.user_pin",
"User PIN"},
48 {
"mod_gpg.admin_pin",
"Admin PIN"},
49 {
"mod_gpg.slot_error",
"Slot map error"},
50 {
"mod_gpg.name",
"Name"},
51 {
"mod_gpg.email",
"Email (optional)"},
52 {
"mod_gpg.curve",
"Curve"},
53 {
"mod_gpg.curve_ed25519",
"Ed25519"},
54 {
"mod_gpg.curve_p256",
"P-256"},
55 {
"mod_gpg.no_key",
"No key configured"},
56 {
"mod_gpg.confirm_reset",
"Reset all GPG keys?"},
57 {
"mod_gpg.export_title",
"GPG Public Key"},
58 {
"mod_gpg.send",
"Send Key"},
59 {
"mod_gpg.received",
"Received Keys"},
60 {
"mod_gpg.recv_none",
"No received keys"},
61 {
"mod_gpg.recv_title",
"Received Keys"},
62 {
"mod_gpg.recv_details",
"Key Details"},
63 {
"mod_gpg.recv_sign",
"Cross-Sign"},
64 {
"mod_gpg.recv_export",
"Export"},
65 {
"mod_gpg.recv_delete",
"Delete"},
66 {
"mod_gpg.recv_confirm_sign",
"Sign this key?"},
67 {
"mod_gpg.recv_confirm_del",
"Delete this key?"},
68 {
"mod_gpg.recv_signed",
"Signed"},
69 {
"mod_gpg.recv_unsigned",
"Unsigned"},
70 {
"mod_gpg.recv_export_title",
"Signed Key Export"},
71 {
"mod_gpg.toast_signed",
"Cross-signed"},
72 {
"mod_gpg.toast_sign_fail",
"Sign failed"},
73 {
"mod_gpg.toast_deleted",
"Deleted"},
94 {
"STATUS",
"",
"Show keys, fingerprints, counters",
cmd_gpg_status},
95 {
"GENERATE",
"<curve> <user_id>",
"Generate SIG+DEC+AUT keys (curve 1=Ed25519, 2=P-256)",
cmd_gpg_generate},
96 {
"EXPORT",
"",
"Print primary + subkey public keys as PEM",
cmd_gpg_export},
97 {
"RESET",
"[token]",
"Two-step destructive reset of all GPG keys",
cmd_gpg_reset},
102 {
"EXPORT_SIGNED",
"<index>",
"Export signed key as ASCII-armored OpenPGP block",
cmd_gpg_export_signed},
103 {
nullptr,
nullptr,
nullptr,
nullptr},
117 registry.registerCommand({
"GPG",
118 "GPG card: STATUS/GENERATE/EXPORT/RESET",
145 char curveBuf[8] = {};
148 const char* p = args;
149 while (p && *p && std::isspace(
static_cast<unsigned char>(*p))) p++;
156 while (p[i] && !std::isspace(
static_cast<unsigned char>(p[i])) && i + 1 <
sizeof(curveBuf)) {
162 while (p && *p && std::isspace(
static_cast<unsigned char>(*p))) p++;
167 strncpy(userId, p,
sizeof(userId) - 1);
199 const uint64_t now =
static_cast<uint64_t
>(esp_timer_get_time());
203 if (args && *args && token_active && strcmp(args,
s_reset_token) == 0) {
212 esp_fill_random(r,
sizeof(r));
216 "WARNING: this wipes ALL GPG keys (SIG/DEC/AUT), the DEC backup, and PINs.\r\n"
217 "Confirm within 30s: GPG RESET %s\r\n",
221static void fp_to_hex(
const uint8_t* fp,
size_t len,
char* out,
size_t out_size) {
222 if (out_size < len * 2 + 1) {
223 if (out_size > 0) out[0] =
'\0';
226 for (
size_t i = 0; i < len; ++i) {
227 snprintf(out + i * 2, 3,
"%02X", fp[i]);
242 for (uint8_t i = 0; i < n; ++i) {
244 if (!store.getKey(i, &key))
continue;
250 key.
sig_len > 0 ?
"Yes" :
"No");
255 if (!args || !*args)
return false;
257 long v = strtol(args, &end, 10);
258 if (end == args || v < 0 || v > 255)
return false;
259 *out =
static_cast<uint8_t
>(v);
283 strftime(ts_buf,
sizeof(ts_buf),
"%Y-%m-%d %H:%M:%S UTC", &tm_v);
296 char sig_hex[2 * 64 + 1];
310 :
"ERROR: delete failed\r\n");
324 uint32_t now =
static_cast<uint32_t
>(time(
nullptr));
325 uint8_t sig[64] = {0};
377 {
nullptr, 0,
false,
nullptr },
378 {
nullptr, 0,
false,
nullptr },
412 switch (
static_cast<GpgMenuAction>(
reinterpret_cast<uintptr_t
>(userData))) {
425 return { label, 0,
false,
reinterpret_cast<void*
>(
static_cast<uintptr_t
>(action)) };
573 snprintf(fp_hex + i * 2, 3,
"%02X", status.
fingerprint[i]);
576 :
ui::tr(
"mod_gpg.curve_p256");
577 static char detail[512];
578 snprintf(detail,
sizeof(detail),
579 "User-ID: %s\nCurve: %s\nFP: %s\nCreated: %lu\nSign Count: %lu",
580 status.
user_id, curveName, fp_hex,
581 static_cast<unsigned long>(status.
created_at),
582 static_cast<unsigned long>(status.
sign_count));
619 {
nullptr, 0,
false,
nullptr },
620 {
nullptr, 0,
false,
nullptr },
622 curveItems[0].
label =
ui::tr(
"mod_gpg.curve_ed25519");
623 curveItems[1].
label =
ui::tr(
"mod_gpg.curve_p256");
639 if (email_len == 0) {
642 size_t max_len =
sizeof(
user_id) - 1;
643 size_t name_fit = name_len > max_len ? max_len : name_len;
644 size_t email_fit = 0;
645 if (name_fit < max_len) {
646 size_t remaining = max_len - name_fit;
648 email_fit = remaining - 3;
651 if (email_fit == 0) {
681 EXT_RAM_BSS_ATTR
static char pem_buf[2048];
683 static char detail_buf[160];
693 detail_buf[0] =
'\0';
702 snprintf(detail_buf,
sizeof(detail_buf),
"%s\n%s", curveName, alchemy);
706 title_buf[0] ? title_buf :
nullptr,
707 detail_buf[0] ? detail_buf :
nullptr);
774 for (uint8_t i = 0; i < n; ++i) {
776 if (!store.getKey(i, &k)) {
779 const char* tag = (k.
sig_len > 0) ?
"[x]" :
"[ ]";
784 reinterpret_cast<void*
>(
static_cast<uintptr_t
>(i)) };
802 char fp_grouped[64] = {};
805 for (
size_t i = 0; i < 40 && pos <
sizeof(fp_grouped) - 6; i += 4) {
806 std::memcpy(fp_grouped + pos, fp_v4 + i, 4);
808 if (i + 4 < 40) fp_grouped[pos++] =
' ';
810 fp_grouped[pos] =
'\0';
814 ?
ui::tr(
"mod_gpg.curve_ed25519")
815 :
ui::tr(
"mod_gpg.curve_p256");
821 strftime(timeBuf,
sizeof(timeBuf),
"%Y-%m-%d %H:%M", &tm_v);
824 "%s\n%s\nFP: %s\nRcvd: %s\n%s",
830 ?
ui::tr(
"mod_gpg.recv_signed")
831 :
ui::tr(
"mod_gpg.recv_unsigned"));
847 const uintptr_t action =
reinterpret_cast<uintptr_t
>(userData);
875 uint32_t now =
static_cast<uint32_t
>(time(
nullptr));
876 uint8_t sig[64] = {0};
907 static GpgModule inst;
916 LOG_I(
TAG,
"Initializing GPG module");
921 if (!slotRange_.hasEcc) {
927 if (slotRange_.hasRmem) {
952 spec.
name =
"OpenPGP SmartCard";
964 LOG_W(
TAG,
"Failed to register CCID interface");
1014 if (!items || maxItems == 0)
return 0;
1037 auto&
module = cdc::mod_gpg::GpgModule::instance();
void mod_gpg_register()
Registers GPG module initializer in global registry.
void gpg_storage_set_rmem_range(uint16_t rmemStart, uint16_t rmemEnd)
bool gpg_storage_ready(void)
void gpg_storage_set_slot_range(uint16_t eccStart, uint16_t eccEnd)
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
#define KEY_FINGERPRINT_MAX_LEN
Shared RAII wrappers for firmware resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_I(tag, fmt,...)
void reportModuleError(const char *name, const char *message)
Records and publishes an operational module error by module name.
bool registerModule(IModule *module)
Registers a module instance in the runtime registry.
static ModuleRegistry & instance()
Returns the singleton module registry instance.
void registerInitializer(ModuleInitFunc initFunc)
Registers a deferred module initializer callback.
void clearModuleErrorByName(const char *name)
Clears stored module error by module name.
static constexpr uint8_t PIN_MAX
static constexpr uint8_t PW3_MIN
static constexpr uint8_t PW1_MIN
static UsbManager & instance()
Returns singleton USB manager instance.
void unregisterInterface(UsbHidInterface type, const char *moduleName)
Unregisters a previously registered HID interface.
uint8_t getMenuItems(core::ModuleMenuItem *items, uint8_t maxItems) override
Provides main-menu entry for GPG module.
const char * getName() const override
bool init() override
Initializes GPG module resources and slot assignments.
bool start() override
Starts GPG module and registers USB CCID interface.
static GpgModule & instance()
Returns singleton GPG module instance.
core::IModule::SlotRequest getSlotRequest() const override
Declares slot requirements for GPG module.
void stop() override
Stops GPG module and unregisters CCID interface.
void setSlotRange(const core::IModule::SlotRange &range) override
Stores slot range assigned by module registry.
bool deleteKey(uint8_t index)
Remove one key by sorted index. No-op if index is out of range.
bool getKey(uint8_t index, gpg_recv_key_t *out)
Load one key by sorted index (0..count()-1).
static GpgRecvStore & instance()
static constexpr uint8_t kMaxKeys
Hard ceiling. Past this addKey rejects further inserts.
static void printf(const char *format,...) __attribute__((format(printf
Prints formatted text to console.
static I18n & instance()
Singleton accessor.
void registerEnglishTable(const I18nEntry *entries, std::size_t count)
Append English entries to the lookup table.
static ViewStack & instance()
Returns singleton view-stack instance.
void push(IView *view, void *context=nullptr)
#define CDC_CURVE_ED25519
uint8_t user_id[FIDO2_USER_ID_MAX_LEN]
bool gpg_set_pending_user_id(const char *user_id)
Stages a user-id string for the next on-device key generation. The string is forwarded to OpenpgpNvsS...
bool gpg_generate_key(uint8_t curve)
Generates SIG / DEC / AUT keys on the device and announces them to the OpenPGP card application (fing...
bool gpg_alchemy_fingerprint(char *buf, size_t len)
Writes the alchemical-word fingerprint of the SIG public key.
bool gpg_get_status(gpg_status_t *status)
Fills status from the OpenPGP card-application state.
bool gpg_export_pubkey_pem(char *buf, size_t size, size_t *out_len)
Renders the current SIG public key as a SubjectPublicKeyInfo PEM. The key is read straight from the s...
#define GPG_FINGERPRINT_LEN
bool gpg_reset(void)
Factory-resets all GPG key material and metadata.
PsramUniquePtr< T > psramAlloc(std::size_t count) noexcept
Allocate count elements of T in PSRAM (8-bit capable region).
static void onReceivedDeleteConfirm(void *)
static char s_recvListLabels[GpgRecvStore::kMaxKeys][72]
static ui::ListItem s_settingsItems[]
static ui::ListView s_recvActionView
static ui::ListItem makeMenuItem(const char *label, GpgMenuAction action)
static void cmd_gpg_recv_info(const char *args)
static uint8_t gpg_retries_pw3()
Returns remaining retries for OpenPGP PW3.
static bool gpg_change_pw1(const char *, const char *newPin)
Changes OpenPGP PW1 value.
static ui::QRCodeView s_qrView
static void showSettings()
Shows GPG settings menu.
static constexpr uint64_t RESET_TOKEN_TIMEOUT_US
static uint64_t s_reset_token_ts_us
static void showSendKey()
static ui::ListItem s_menuItems[8]
static void cmd_gpg_status(const char *args)
Serial command printing current GPG key status.
static void showReceivedDetail()
static bool s_viewsInitialized
static void registerStrings()
static bool gpg_verify_pw1(const char *pin)
Verifies OpenPGP PW1 using persistent pin-storage backend.
static void onReceivedSignConfirm(void *)
static ui::ListView s_settingsView
static ui::ListView s_menuView
static void onWizardEmail(const char *text)
Saves wizard email and opens curve selection.
static void cmd_gpg_cross_sign(const char *args)
static gpg_recv_key_t s_recvSelectedKey
static bool gpg_change_pw3(const char *, const char *newPin)
Changes OpenPGP PW3 value.
constexpr ui::I18nEntry kStrings[]
static void onMenuSelect(uint16_t, void *userData)
Handles GPG main-menu selections via the entry's userData tag.
static bool gpg_blocked_pw1()
Returns whether OpenPGP PW1 is blocked.
static void rebuildMenu()
Rebuilds GPG main menu labels and populates userData tags.
static void showExport()
Exports public key to serial output and QR view.
static void onResetConfirm(void *)
Confirm callback resetting all GPG key material.
static WizardState s_wizard
static void cmd_gpg_recv_list(const char *args)
static void onWizardCurve(uint16_t index, void *)
Finalizes wizard curve selection and triggers key generation.
static ui::ListView s_recvListView
Received-keys list UI state.
constexpr uint8_t kGpgRecvFlagVerified
static void cmd_gpg(const char *args)
static void showStatus()
Displays current GPG key status and metadata.
bool ble_gpg_xsig_init()
Initialise the GPG cross-sign BLE endpoint.
static char s_reset_token[7]
Serial command resetting GPG key material.
static void rebuildReceivedList()
static void registerCommands()
Registers serial commands exposed by GPG module.
static void cmd_gpg_recv_delete(const char *args)
void ble_gpg_xsig_set_received_callback(XsigReceivedCallback cb)
Install / remove the "key received" notification.
static void onReceivedListSelect(uint16_t index, void *)
static constexpr const char * CMD_MODULE
static ui::PinChangeView s_pinChangeView
static ui::ListView s_curveView
static uint8_t gpg_retries_pw1()
Returns remaining retries for OpenPGP PW1.
static void showReceivedKeys()
bool gpgCrossSign(const gpg_recv_key_t &target, uint32_t sig_creation_time, uint8_t out_sig[64])
Cross-sign a received key with the badge's own SIG ECC slot.
static const cdc::serial::SubCommand kGpgSubs[]
static void onReceivedActionSelect(uint16_t index, void *)
static void cmd_gpg_export(const char *args)
Serial command exporting GPG public key in PEM format.
static void wizardStart()
Starts key-generation wizard flow.
static void cmd_gpg_reset(const char *args)
static void onGpgPinComplete(bool)
Pin-change completion callback returning to previous view.
static uint8_t s_recvSelectedIndex
static bool s_commandsRegistered
static bool gpg_blocked_pw3()
Returns whether OpenPGP PW3 is blocked.
static ui::ListItem s_recvListItems[GpgRecvStore::kMaxKeys+1]
static char s_recvExportBuf[4096]
static bool parse_index(const char *args, uint8_t *out)
static void confirmReset()
Opens reset confirmation dialog.
static ui::T9InputView s_t9Input
static void cmd_gpg_export_signed(const char *args)
static void onSettingsSelect(uint16_t index, void *)
Handles settings-menu selection for PW1/PW3 change flow.
static char s_recvDetailText[640]
static bool gpg_verify_pw3(const char *pin)
Verifies OpenPGP PW3 using persistent pin-storage backend.
bool gpgBuildSignedKeyArmored(const gpg_recv_key_t &key, char *out, size_t out_size, size_t *out_len)
Build an ASCII-armored OpenPGP block carrying the cross-signed key.
static void onWizardName(const char *text)
Saves wizard name and opens email step.
static ui::ListItem s_recvActionItems[3]
static void fp_to_hex(const uint8_t *fp, size_t len, char *out, size_t out_size)
static void cmd_gpg_generate(const char *args)
Serial command generating GPG key with selected curve and user-id.
static ui::InfoView s_infoView
ICommandRegistry & getCommandRegistry()
Returns singleton command-registry interface.
void dispatchSubCommand(const char *parent, const char *args, const SubCommand *table)
Routes a sub-command line to its handler.
const char * tr(const char *key)
Look up a translation by string key.
void showToast(const char *message, uint16_t durationMs=1500)
Shows a plain toast message.
InfoView * showInfo(const char *title, const char *text, const char *hint=nullptr)
Shows a shared info view instance and pushes it onto the view stack.
void showConfirm(const char *message, ConfirmView::ConfirmCallback onConfirm, ConfirmView::CancelCallback onCancel=nullptr, ConfirmView::Icon icon=ConfirmView::Icon::QUESTION, void *userData=nullptr)
Shows a shared modal confirmation dialog instance.
void showToastSuccess(const char *message, uint16_t durationMs=1500)
Shows a success toast message.
void showToastError(const char *message, uint16_t durationMs=1500)
Shows an error toast message.
bool openpgp_has_any_key(void)
Reports whether any of the SIG / DEC / AUT roles has a non-zero fingerprint configured....
uint8_t pin_storage_openpgp_pw1_retries(void)
bool pin_storage_openpgp_pw1_blocked(void)
bool pin_storage_openpgp_change_pw3(const char *new_pin)
uint8_t pin_storage_openpgp_pw3_retries(void)
bool pin_storage_openpgp_verify_pw1(const char *pin)
bool pin_storage_openpgp_verify_pw3(const char *pin)
bool pin_storage_openpgp_change_pw1(const char *new_pin)
bool pin_storage_openpgp_pw3_blocked(void)
One GPG public key received from another badge.
uint8_t fingerprint_v4[20]
uint8_t fingerprint_v5[32]
Snapshot of the current OpenPGP card-application state for UI display.
Single English translation entry.