aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsaturneric <[email protected]>2024-07-28 08:18:33 +0000
committersaturneric <[email protected]>2024-07-28 08:18:33 +0000
commitc27c541257585878e4db705559c5cb953dc860b7 (patch)
tree7dc69115de96151a40acfc8891bf9a065eee0a9b
parentfix: solve a memory leak issue (diff)
downloadModules-c27c541257585878e4db705559c5cb953dc860b7.tar.gz
Modules-c27c541257585878e4db705559c5cb953dc860b7.zip
feat: add pinentry module and paper key module
-rw-r--r--include/GFModuleCommonUtils.hpp117
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/m_gpg_info/CMakeLists.txt3
-rw-r--r--src/m_gpg_info/GnuPGInfoGatheringModule.cpp7
-rw-r--r--src/m_paper_key/CMakeLists.txt50
-rw-r--r--src/m_paper_key/PaperKeyModule.cpp184
-rw-r--r--src/m_paper_key/PaperKeyModule.h56
-rw-r--r--src/m_paper_key/extract.cpp93
-rw-r--r--src/m_paper_key/extract.h26
-rw-r--r--src/m_paper_key/output.cpp304
-rw-r--r--src/m_paper_key/output.h37
-rw-r--r--src/m_paper_key/packets.cpp60
-rw-r--r--src/m_paper_key/packets.h34
-rw-r--r--src/m_paper_key/paperkey.cpp87
-rw-r--r--src/m_paper_key/parse.cpp537
-rw-r--r--src/m_paper_key/parse.h37
-rw-r--r--src/m_paper_key/restore.cpp181
-rw-r--r--src/m_paper_key/restore.h27
-rw-r--r--src/m_pinentry/CMakeLists.txt63
-rw-r--r--src/m_pinentry/GpgPassphraseContext.cpp57
-rw-r--r--src/m_pinentry/GpgPassphraseContext.h61
-rw-r--r--src/m_pinentry/PinentryModule.cpp106
-rw-r--r--src/m_pinentry/PinentryModule.h56
-rw-r--r--src/m_pinentry/RaisePinentry.cpp107
-rw-r--r--src/m_pinentry/RaisePinentry.h58
-rw-r--r--src/m_pinentry/accessibility.cpp44
-rw-r--r--src/m_pinentry/accessibility.h40
-rw-r--r--src/m_pinentry/capslock/capslock.cpp41
-rw-r--r--src/m_pinentry/capslock/capslock.h77
-rw-r--r--src/m_pinentry/capslock/capslock_unix.cpp137
-rw-r--r--src/m_pinentry/capslock/capslock_win.cpp26
-rw-r--r--src/m_pinentry/icons/data-error.svg9
-rw-r--r--src/m_pinentry/icons/document-encrypt.pngbin0 -> 1835 bytes
-rw-r--r--src/m_pinentry/icons/hint.svg13
-rw-r--r--src/m_pinentry/icons/password-generate.svg13
-rw-r--r--src/m_pinentry/icons/visibility.svg21
-rw-r--r--src/m_pinentry/pinentry.cpp304
-rw-r--r--src/m_pinentry/pinentry.h339
-rw-r--r--src/m_pinentry/pinentry.qrc10
-rw-r--r--src/m_pinentry/pinentry_debug.cpp31
-rw-r--r--src/m_pinentry/pinentry_debug.h28
-rw-r--r--src/m_pinentry/pinentryconfirm.cpp123
-rw-r--r--src/m_pinentry/pinentryconfirm.h63
-rw-r--r--src/m_pinentry/pinentrydialog.cpp635
-rw-r--r--src/m_pinentry/pinentrydialog.h169
-rw-r--r--src/m_pinentry/pinlineedit.cpp204
-rw-r--r--src/m_pinentry/pinlineedit.h60
-rw-r--r--src/m_pinentry/qti18n.cpp93
-rw-r--r--src/m_pinentry/secmem++.h91
-rw-r--r--src/m_ver_check/VersionCheckingModule.cpp9
50 files changed, 4916 insertions, 16 deletions
diff --git a/include/GFModuleCommonUtils.hpp b/include/GFModuleCommonUtils.hpp
index 392aaef..d65b356 100644
--- a/include/GFModuleCommonUtils.hpp
+++ b/include/GFModuleCommonUtils.hpp
@@ -29,6 +29,7 @@
#pragma once
#include <GFSDKUI.h>
+#include <qsharedpointer.h>
#include <QMap>
#include <QString>
@@ -113,6 +114,71 @@ inline auto QMapToGFModuleMetaDataList(const QMap<QString, QString>& map)
return head;
}
+inline auto ConvertEventParamsToMap(GFModuleEventParam* params)
+ -> QMap<QString, QString> {
+ QMap<QString, QString> param_map;
+ GFModuleEventParam* current = params;
+ GFModuleEventParam* last;
+
+ while (current != nullptr) {
+ param_map[current->name] = UDUP(current->value);
+
+ last = current;
+ current = current->next;
+ GFFreeMemory(last);
+ }
+
+ return param_map;
+}
+
+inline auto ConvertMapToParams(const QMap<QString, QString>& param_map)
+ -> GFModuleEventParam* {
+ GFModuleEventParam* head = nullptr;
+ GFModuleEventParam* prev = nullptr;
+
+ for (const auto& [key, value] : param_map.asKeyValueRange()) {
+ auto* param = static_cast<GFModuleEventParam*>(
+ GFAllocateMemory(sizeof(GFModuleEventParam)));
+
+ param->name = DUP(key.toUtf8());
+ param->value = DUP(value.toUtf8());
+ param->next = nullptr;
+
+ if (prev == nullptr) {
+ head = param;
+ } else {
+ prev->next = param;
+ }
+ prev = param;
+ }
+
+ return head;
+}
+
+inline auto ConvertEventToMap(GFModuleEvent* event) -> QMap<QString, QString> {
+ QMap<QString, QString> event_map;
+
+ event_map["event_id"] = UDUP(event->id);
+ event_map["trigger_id"] = UDUP(event->trigger_id);
+ event_map.insert(ConvertEventParamsToMap(event->params));
+
+ GFFreeMemory(event);
+
+ return event_map;
+}
+
+inline auto ConvertMapToEvent(QMap<QString, QString> event_map)
+ -> GFModuleEvent* {
+ auto* event =
+ static_cast<GFModuleEvent*>(GFAllocateMemory(sizeof(GFModuleEvent)));
+
+ event->id = DUP(event_map["event_id"].toUtf8());
+ event->trigger_id = DUP(event_map["trigger_id"].toUtf8());
+ event->params = nullptr;
+
+ return event;
+}
+
inline auto AllocBufferAndCopy(const QByteArray& b) -> char* {
auto* p = static_cast<char*>(GFAllocateMemory(sizeof(char) * b.size()));
memcpy(p, b.constData(), b.size());
@@ -136,6 +202,55 @@ auto SecureCreateSharedObject(Args&&... args) -> std::shared_ptr<T> {
}
}
+template <typename T>
+class PointerConverter {
+ public:
+ explicit PointerConverter(void* ptr) : ptr_(ptr) {}
+
+ auto AsType() const -> T* { return static_cast<T*>(ptr_); }
+
+ private:
+ void* ptr_;
+};
+
+/**
+ * @brief
+ *
+ * @tparam T
+ * @return T*
+ */
+template <typename T>
+auto SecureMallocAsType(std::size_t size) -> T* {
+ return PointerConverter<T>(GFAllocateMemory(size)).AsType();
+}
+
+/**
+ * @brief
+ *
+ * @return void*
+ */
+template <typename T>
+auto SecureReallocAsType(T* ptr, std::size_t size) -> T* {
+ return PointerConverter<T>(GFReallocateMemory(ptr, size)).AsType();
+}
+
+template <typename T, typename... Args>
+auto SecureCreateQSharedObject(Args&&... args) -> QSharedPointer<T> {
+ void* mem = GFAllocateMemory(sizeof(T));
+ if (!mem) throw std::bad_alloc();
+
+ try {
+ T* obj = new (mem) T(std::forward<Args>(args)...);
+ return QSharedPointer<T>(obj, [](T* ptr) {
+ ptr->~T();
+ GFFreeMemory(ptr);
+ });
+ } catch (...) {
+ GFFreeMemory(mem);
+ throw;
+ }
+}
+
inline auto CharArrayToQStringList(char** pl_components,
int size) -> QStringList {
QStringList list;
@@ -145,4 +260,4 @@ inline auto CharArrayToQStringList(char** pl_components,
}
GFFreeMemory(pl_components);
return list;
-} \ No newline at end of file
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index be2d07d..f3861d8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -25,4 +25,6 @@
# modules
add_subdirectory(m_ver_check)
-add_subdirectory(m_gpg_info) \ No newline at end of file
+add_subdirectory(m_gpg_info)
+add_subdirectory(m_pinentry)
+add_subdirectory(m_paper_key) \ No newline at end of file
diff --git a/src/m_gpg_info/CMakeLists.txt b/src/m_gpg_info/CMakeLists.txt
index ec68799..37f36e4 100644
--- a/src/m_gpg_info/CMakeLists.txt
+++ b/src/m_gpg_info/CMakeLists.txt
@@ -49,9 +49,6 @@ endif()
# using std c++ 17
target_compile_features(mod_gpg_info PRIVATE cxx_std_17)
-# ui
-set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_AUTOUIC_SEARCH_PATHS} ${CMAKE_CURRENT_SOURCE_DIR}/ui)
-
# i18n
set(LOCALE_TS_PATH ${CMAKE_CURRENT_SOURCE_DIR}/ts)
set(TS_FILES "${LOCALE_TS_PATH}/ModuleGnuPGInfoGathering.en_US.ts"
diff --git a/src/m_gpg_info/GnuPGInfoGatheringModule.cpp b/src/m_gpg_info/GnuPGInfoGatheringModule.cpp
index 4f2a9a0..142a7d1 100644
--- a/src/m_gpg_info/GnuPGInfoGatheringModule.cpp
+++ b/src/m_gpg_info/GnuPGInfoGatheringModule.cpp
@@ -109,11 +109,8 @@ auto GFExecuteModule(GFModuleEvent *event) -> int {
StartGatheringGnuPGInfo();
- char **event_argv =
- static_cast<char **>(GFAllocateMemory(sizeof(char **) * 1));
- event_argv[0] = DUP("0");
-
- GFModuleTriggerModuleEventCallback(event, GFGetModuleID(), 1, event_argv);
+ GFModuleTriggerModuleEventCallback(event, GFGetModuleID(), 1,
+ ConvertMapToParams({{"ret", "0"}}));
MLogDebug("gnupg external info gathering done");
return 0;
diff --git a/src/m_paper_key/CMakeLists.txt b/src/m_paper_key/CMakeLists.txt
new file mode 100644
index 0000000..0855aa8
--- /dev/null
+++ b/src/m_paper_key/CMakeLists.txt
@@ -0,0 +1,50 @@
+# Copyright (C) 2021 Saturneric <[email protected]>
+#
+# This file is part of GpgFrontend.
+#
+# GpgFrontend 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.
+#
+# GpgFrontend 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+#
+# The initial version of the source code is inherited from
+# the gpg4usb project, which is under GPL-3.0-or-later.
+#
+# All the source code of GpgFrontend was modified and released by
+# Saturneric <[email protected]> starting on May 12, 2021.
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# com.bktus.gpgfrontend.module.integrated.gnupg_info_gathering
+
+aux_source_directory(. INTEGRATED_MODULE_SOURCE)
+
+# define libgpgfrontend_module
+add_library(mod_paper_key SHARED ${INTEGRATED_MODULE_SOURCE})
+
+# install dir
+install(TARGETS mod_paper_key
+ LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/modules")
+
+# link sdk
+target_link_libraries(mod_paper_key PRIVATE
+ gpgfrontend_module_sdk)
+
+if(GPGFRONTEND_QT5_BUILD)
+ # link Qt core
+ target_link_libraries(mod_paper_key PRIVATE Qt5::Core)
+else()
+ # link Qt core
+ target_link_libraries(mod_paper_key PRIVATE Qt6::Core)
+endif()
+
+# using std c++ 17
+target_compile_features(mod_paper_key PRIVATE cxx_std_17)
diff --git a/src/m_paper_key/PaperKeyModule.cpp b/src/m_paper_key/PaperKeyModule.cpp
new file mode 100644
index 0000000..c6eedb7
--- /dev/null
+++ b/src/m_paper_key/PaperKeyModule.cpp
@@ -0,0 +1,184 @@
+/**
+ * Copyright (C) 2021 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend 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.
+ *
+ * GpgFrontend 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#include "PaperKeyModule.h"
+
+#include <QtCore>
+
+#include "GFModuleCommonUtils.hpp"
+#include "GFSDKBuildInfo.h"
+#include "extract.h"
+
+auto GFGetModuleGFSDKVersion() -> const char * {
+ return DUP(GF_SDK_VERSION_STR);
+}
+
+auto GFGetModuleQtEnvVersion() -> const char * { return DUP(QT_VERSION_STR); }
+
+auto GFGetModuleID() -> const char * {
+ return DUP("com.bktus.gpgfrontend.module.paper_key");
+}
+
+auto GFGetModuleVersion() -> const char * { return DUP("1.0.0"); }
+
+auto GFGetModuleMetaData() -> GFModuleMetaData * {
+ return QMapToGFModuleMetaDataList(
+ {{"Name", "PaperKey"},
+ {"Description", "Integrated PaperKey Functions."},
+ {"Author", "Saturneric"}});
+}
+
+auto GFRegisterModule() -> int {
+ MLogDebug("paper key module registering");
+
+ return 0;
+}
+
+auto GFActiveModule() -> int {
+ MLogDebug("paper key module activating");
+ GFModuleListenEvent(GFGetModuleID(), DUP("REQUEST_TRANS_KEY_2_PAPER_KEY"));
+ GFModuleListenEvent(GFGetModuleID(), DUP("REQUEST_TRANS_PAPER_KEY_2_KEY"));
+ return 0;
+}
+
+auto GFExecuteModule(GFModuleEvent *p_event) -> int {
+ MLogDebug(
+ QString("paper key module executing, event id: %1").arg(p_event->id));
+
+ auto event = ConvertEventToMap(p_event);
+
+ if (event["event_id"] == "REQUEST_TRANS_KEY_2_PAPER_KEY") {
+ if (event["secret_key"].isEmpty() || event["output_path"].isEmpty()) {
+ GFModuleTriggerModuleEventCallback(
+ ConvertMapToEvent(event), GFGetModuleID(), 1,
+ ConvertMapToParams(
+ {{"ret", "-1"},
+ {"reason", "secret key or output path is empty"}}));
+ return -1;
+ }
+
+ QByteArray secret_key_data =
+ QByteArray::fromBase64(event["secret_key"].toUtf8());
+
+ QTemporaryFile secret_key_t_file;
+ if (!secret_key_t_file.open()) {
+ qWarning() << "Unable to open temporary file";
+ MLogWarn("unable to open temporary file");
+ return -1;
+ }
+
+ secret_key_t_file.write(secret_key_data);
+ secret_key_t_file.flush();
+ secret_key_t_file.seek(0);
+
+ FILE *file = fdopen(secret_key_t_file.handle(), "rb");
+ if (file == nullptr) {
+ qDebug() << "Unable to convert QTemporaryFile to FILE*";
+ return -1;
+ }
+
+ extract(file, event["output_path"].toUtf8(), AUTO);
+
+ fclose(file);
+ } else if (event["event_id"] == "REQUEST_TRANS_PAPER_KEY_2_KEY") {
+ if (event["public_key"].isEmpty() || event["paper_key_secrets"].isEmpty()) {
+ GFModuleTriggerModuleEventCallback(
+ ConvertMapToEvent(event), GFGetModuleID(), 1,
+ ConvertMapToParams(
+ {{"ret", "-1"},
+ {"reason", "public key or paper key secrets is empty"}}));
+ return -1;
+ }
+
+ QByteArray public_key_data =
+ QByteArray::fromBase64(event["public_key"].toUtf8());
+
+ QTemporaryFile public_key_t_file;
+ if (!public_key_t_file.open()) {
+ GFModuleTriggerModuleEventCallback(
+ ConvertMapToEvent(event), GFGetModuleID(), 1,
+ ConvertMapToParams(
+ {{"ret", "-1"}, {"reason", "unable to open temporary file"}}));
+ return -1;
+ }
+
+ public_key_t_file.write(public_key_data);
+ public_key_t_file.flush();
+ public_key_t_file.seek(0);
+
+ FILE *pubring = fdopen(public_key_t_file.handle(), "rb");
+ if (pubring == nullptr) {
+ GFModuleTriggerModuleEventCallback(
+ ConvertMapToEvent(event), GFGetModuleID(), 1,
+ ConvertMapToParams(
+ {{"ret", "-1"},
+ {"reason", "unable to convert QTemporaryFile to FILE*"}}));
+ return -1;
+ }
+
+ QByteArray secrets_data =
+ QByteArray::fromBase64(event["paper_key_secrets"].toUtf8());
+
+ QTemporaryFile secrets_data_file;
+ if (!secrets_data_file.open()) {
+ GFModuleTriggerModuleEventCallback(
+ ConvertMapToEvent(event), GFGetModuleID(), 1,
+ ConvertMapToParams(
+ {{"ret", "-1"}, {"reason", "unable to open temporary file"}}));
+ return -1;
+ }
+
+ secrets_data_file.write(public_key_data);
+ secrets_data_file.flush();
+ secrets_data_file.seek(0);
+
+ FILE *secrets = fdopen(secrets_data_file.handle(), "rb");
+ if (secrets == nullptr) {
+ GFModuleTriggerModuleEventCallback(
+ ConvertMapToEvent(event), GFGetModuleID(), 1,
+ ConvertMapToParams(
+ {{"ret", "-1"},
+ {"reason", "unable to convert QTemporaryFile to FILE*"}}));
+ return -1;
+ }
+
+ restore(pubring, secrets, AUTO, )
+ }
+
+ GFModuleTriggerModuleEventCallback(ConvertMapToEvent(event), GFGetModuleID(),
+ 1, ConvertMapToParams({{"ret", "0"}}));
+ return 0;
+}
+
+auto GFDeactiveModule() -> int { return 0; }
+
+auto GFUnregisterModule() -> int {
+ MLogDebug("paper key module unregistering");
+
+ return 0;
+} \ No newline at end of file
diff --git a/src/m_paper_key/PaperKeyModule.h b/src/m_paper_key/PaperKeyModule.h
new file mode 100644
index 0000000..35ee4ac
--- /dev/null
+++ b/src/m_paper_key/PaperKeyModule.h
@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 2021 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend 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.
+ *
+ * GpgFrontend 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <GFSDKModule.h>
+
+#include "GFModuleExport.h"
+
+extern "C" {
+
+auto GF_MODULE_EXPORT GFGetModuleGFSDKVersion() -> const char *;
+
+auto GF_MODULE_EXPORT GFGetModuleQtEnvVersion() -> const char *;
+
+auto GF_MODULE_EXPORT GFGetModuleID() -> const char *;
+
+auto GF_MODULE_EXPORT GFGetModuleVersion() -> const char *;
+
+auto GF_MODULE_EXPORT GFGetModuleMetaData() -> GFModuleMetaData *;
+
+auto GF_MODULE_EXPORT GFRegisterModule() -> int;
+
+auto GF_MODULE_EXPORT GFActiveModule() -> int;
+
+auto GF_MODULE_EXPORT GFExecuteModule(GFModuleEvent *) -> int;
+
+auto GF_MODULE_EXPORT GFDeactiveModule() -> int;
+
+auto GF_MODULE_EXPORT GFUnregisterModule() -> int;
+};
diff --git a/src/m_paper_key/extract.cpp b/src/m_paper_key/extract.cpp
new file mode 100644
index 0000000..388a6c7
--- /dev/null
+++ b/src/m_paper_key/extract.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2007, 2017 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "extract.h"
+
+#include <cstdio>
+
+#include "output.h"
+#include "packets.h"
+#include "parse.h"
+
+extern int verbose;
+
+int extract(FILE *input, const char *outname, enum data_type output_type) {
+ struct packet *packet;
+ int offset;
+ unsigned char fingerprint[20];
+ unsigned char version = 0;
+
+ packet = parse(input, 5, 0);
+ if (!packet) {
+ fprintf(stderr, "Unable to find secret key packet\n");
+ return 1;
+ }
+
+ offset = extract_secrets(packet);
+ if (offset == -1) return 1;
+
+ if (verbose > 1) fprintf(stderr, "Secret offset is %d\n", offset);
+
+ calculate_fingerprint(packet, offset, fingerprint);
+
+ if (verbose) {
+ fprintf(stderr, "Primary key fingerprint: ");
+ print_bytes(stderr, fingerprint, 20);
+ fprintf(stderr, "\n");
+ }
+
+ output_start(outname, output_type, fingerprint);
+ output_bytes(&version, 1);
+ output_bytes(packet->buf, 1);
+ output_bytes(fingerprint, 20);
+ output_length16(packet->len - offset);
+ output_bytes(&packet->buf[offset], packet->len - offset);
+
+ free_packet(packet);
+
+ while ((packet = parse(input, 7, 5))) {
+ offset = extract_secrets(packet);
+ if (offset == -1) return 1;
+
+ if (verbose > 1) fprintf(stderr, "Secret subkey offset is %d\n", offset);
+
+ calculate_fingerprint(packet, offset, fingerprint);
+
+ if (verbose) {
+ fprintf(stderr, "Subkey fingerprint: ");
+ print_bytes(stderr, fingerprint, 20);
+ fprintf(stderr, "\n");
+ }
+
+ output_bytes(packet->buf, 1);
+ output_bytes(fingerprint, 20);
+ output_length16(packet->len - offset);
+ output_bytes(&packet->buf[offset], packet->len - offset);
+
+ free_packet(packet);
+ }
+
+ output_finish();
+
+ if (input == stdin) {
+ /* Consume everything else on input */
+ while ((fgetc(input) != EOF));
+ }
+
+ return 0;
+}
diff --git a/src/m_paper_key/extract.h b/src/m_paper_key/extract.h
new file mode 100644
index 0000000..51cf603
--- /dev/null
+++ b/src/m_paper_key/extract.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include <cstdio>
+
+#include "output.h"
+
+auto extract(FILE *input, const char *outname,
+ enum data_type output_type) -> int;
diff --git a/src/m_paper_key/output.cpp b/src/m_paper_key/output.cpp
new file mode 100644
index 0000000..8c6291b
--- /dev/null
+++ b/src/m_paper_key/output.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2007, 2008, 2009, 2012, 2016 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#ifdef _WIN32
+#include <fcntl.h>
+#include <io.h>
+#endif
+#include "output.h"
+#include "packets.h"
+
+extern unsigned int output_width;
+extern char *comment;
+
+static enum data_type output_type;
+static FILE *output;
+static unsigned int line_items;
+static unsigned long all_crc = CRC24_INIT;
+
+#define CRC24_POLY 0x864CFBL
+
+void do_crc24(unsigned long *crc, const unsigned char *buf, size_t len) {
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ int j;
+
+ *crc ^= buf[i] << 16;
+ for (j = 0; j < 8; j++) {
+ *crc <<= 1;
+ if (*crc & 0x1000000) *crc ^= CRC24_POLY;
+ }
+ }
+}
+
+static void print_base16(const unsigned char *buf, size_t length) {
+ static unsigned long line_crc = CRC24_INIT;
+ static unsigned int line = 0;
+
+ if (buf) {
+ size_t i;
+ static unsigned int offset = 0;
+
+ for (i = 0; i < length; i++, offset++) {
+ if (offset % line_items == 0) {
+ if (line) {
+ fprintf(output, "%06lX\n", line_crc & 0xFFFFFFL);
+ line_crc = CRC24_INIT;
+ }
+
+ fprintf(output, "%3u: ", ++line);
+ }
+
+ fprintf(output, "%02X ", buf[i]);
+
+ do_crc24(&line_crc, &buf[i], 1);
+ }
+ } else {
+ fprintf(output, "%06lX\n", line_crc & 0xFFFFFFL);
+ fprintf(output, "%3u: %06lX\n", line + 1, all_crc & 0xFFFFFFL);
+ }
+}
+
+void print_bytes(FILE *stream, const unsigned char *buf, size_t length) {
+ size_t i;
+
+ for (i = 0; i < length; i++) fprintf(stream, "%02X", buf[i]);
+}
+
+void output_file_format(FILE *stream, const char *prefix) {
+ fprintf(stream, "%sFile format:\n", prefix);
+ fprintf(stream,
+ "%sa) 1 octet: Version of the paperkey format (currently 0).\n",
+ prefix);
+ fprintf(stream,
+ "%sb) 1 octet: OpenPGP key or subkey version (currently 4)\n",
+ prefix);
+ fprintf(stream,
+ "%sc) n octets: Key fingerprint (20 octets for a version 4 key or "
+ "subkey)\n",
+ prefix);
+ fprintf(
+ stream,
+ "%sd) 2 octets: 16-bit big endian length of the following secret data\n",
+ prefix);
+ fprintf(stream,
+ "%se) n octets: Secret data: a partial OpenPGP secret key or subkey "
+ "packet as\n",
+ prefix);
+ fprintf(stream,
+ "%s specified in RFC 4880, starting with the "
+ "string-to-key usage\n",
+ prefix);
+ fprintf(stream,
+ "%s octet and continuing until the end of the packet.\n",
+ prefix);
+ fprintf(stream,
+ "%sRepeat fields b through e as needed to cover all subkeys.\n",
+ prefix);
+ fprintf(stream, "%s\n", prefix);
+ fprintf(
+ stream,
+ "%sTo recover a secret key without using the paperkey program, use the\n",
+ prefix);
+ fprintf(stream,
+ "%skey fingerprint to match an existing public key packet with the\n",
+ prefix);
+ fprintf(stream,
+ "%scorresponding secret data from the paper key. Next, append this "
+ "secret\n",
+ prefix);
+ fprintf(stream,
+ "%sdata to the public key packet. Finally, switch the public key "
+ "packet tag\n",
+ prefix);
+ fprintf(stream,
+ "%sfrom 6 to 5 (14 to 7 for subkeys). This will recreate the "
+ "original secret\n",
+ prefix);
+ fprintf(stream,
+ "%skey or secret subkey packet. Repeat as needed for all public key "
+ "or subkey\n",
+ prefix);
+ fprintf(stream,
+ "%spackets in the public key. All other packets (user IDs, "
+ "signatures, etc.)\n",
+ prefix);
+ fprintf(stream, "%smay simply be copied from the public key.\n", prefix);
+}
+
+int output_start(const char *name, enum data_type type,
+ unsigned char fingerprint[20]) {
+ if (name) {
+ if (type == RAW)
+ output = fopen(name, "wb");
+ else
+ output = fopen(name, "w");
+
+ if (!output) return -1;
+ } else {
+ if (type == RAW) set_binary_mode(stdout);
+
+ output = stdout;
+ }
+
+ output_type = type;
+
+ switch (type) {
+ case RAW:
+ break;
+
+ case AUTO:
+ case BASE16: {
+ time_t now = time(NULL);
+
+ line_items = (output_width - 5 - 6) / 3;
+ fprintf(output, "# Secret portions of key ");
+ print_bytes(output, fingerprint, 20);
+ fprintf(output, "\n");
+ fprintf(output, "# Base16 data extracted %.24s\n", ctime(&now));
+ fprintf(output,
+ "# Created with "
+ "Paper Key Module of GpgFrontend"
+ " by Saturneric\n#\n");
+ output_file_format(output, "# ");
+ fprintf(output,
+ "#\n# Each base16 line ends with a CRC-24 of that line.\n");
+ fprintf(output,
+ "# The entire block of data ends with a CRC-24 of the entire "
+ "block of data.\n\n");
+ // if (comment != nullptr) fprintf(output, "# %s\n\n", comment);
+ } break;
+ }
+
+ return 0;
+}
+
+ssize_t output_bytes(const unsigned char *buf, size_t length) {
+ ssize_t ret = -1;
+
+ do_crc24(&all_crc, buf, length);
+
+ switch (output_type) {
+ case RAW:
+ if (buf == NULL) {
+ unsigned char crc[3];
+
+ crc[0] = (all_crc & 0xFFFFFFL) >> 16;
+ crc[1] = (all_crc & 0xFFFFFFL) >> 8;
+ crc[2] = (all_crc & 0xFFFFFFL);
+
+ ret = fwrite(crc, 1, 3, output);
+ } else
+ ret = fwrite(buf, 1, length, output);
+ break;
+
+ case AUTO:
+ case BASE16:
+ print_base16(buf, length);
+ ret = length;
+ break;
+ }
+
+ return ret;
+}
+
+ssize_t output_length16(size_t length) {
+ unsigned char encoded[2];
+
+ assert(length <= 65535);
+
+ encoded[0] = length >> 8;
+ encoded[1] = length;
+
+ return output_bytes(encoded, 2);
+}
+
+ssize_t output_openpgp_header(unsigned char tag, size_t length) {
+ unsigned char encoded[6];
+ size_t bytes;
+
+ /* We use the same "tag under 16, use old-style packets" rule that
+ many OpenPGP programs do. This helps make the resulting key
+ byte-for-byte identical. It's not a guarantee, as it is legal
+ for the generating program to use whatever packet style it likes,
+ but does help avoid questions why the input to paperkey might not
+ equal the output. */
+
+ if (tag < 16) {
+ if (length > 65535) {
+ encoded[0] = 0x80 | (tag << 2) | 2;
+ encoded[1] = length >> 24;
+ encoded[2] = length >> 16;
+ encoded[3] = length >> 8;
+ encoded[4] = length;
+ bytes = 5;
+ } else if (length > 255) {
+ encoded[0] = 0x80 | (tag << 2) | 1;
+ encoded[1] = length >> 8;
+ encoded[2] = length;
+ bytes = 3;
+ } else {
+ encoded[0] = 0x80 | (tag << 2);
+ encoded[1] = length;
+ bytes = 2;
+ }
+ } else {
+ encoded[0] = 0xC0 | tag;
+
+ if (length > 8383) {
+ encoded[1] = 0xFF;
+ encoded[2] = length >> 24;
+ encoded[3] = length >> 16;
+ encoded[4] = length >> 8;
+ encoded[5] = length;
+ bytes = 6;
+ } else if (length > 191) {
+ encoded[1] = 192 + ((length - 192) >> 8);
+ encoded[2] = (length - 192);
+ bytes = 3;
+ } else {
+ encoded[1] = length;
+ bytes = 2;
+ }
+ }
+
+ return output_bytes(encoded, bytes);
+}
+
+void output_finish(void) {
+ output_bytes(nullptr, 0);
+ if (output != nullptr && output != stdout) fclose(output);
+}
+
+void set_binary_mode(FILE *stream) {
+#ifdef _WIN32
+ if (_setmode(_fileno(stream), _O_BINARY) == -1) {
+ fprintf(stderr, "Unable to set stream mode to binary: %s\n",
+ strerror(errno));
+ exit(1);
+ }
+#else
+ (void)stream;
+#endif
+} \ No newline at end of file
diff --git a/src/m_paper_key/output.h b/src/m_paper_key/output.h
new file mode 100644
index 0000000..21f36d3
--- /dev/null
+++ b/src/m_paper_key/output.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007, 2012, 2016 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include <cstdio>
+
+enum data_type { AUTO, BASE16, RAW };
+
+#define CRC24_INIT 0xB704CEL
+
+void do_crc24(unsigned long *crc, const unsigned char *buf, size_t len);
+void print_bytes(FILE *stream, const unsigned char *buf, size_t length);
+void output_file_format(FILE *stream, const char *prefix);
+int output_start(const char *name, enum data_type type,
+ unsigned char fingerprint[20]);
+ssize_t output_bytes(const unsigned char *buf, size_t length);
+#define output_packet(_packet) output_bytes((_packet)->buf, (_packet)->len)
+ssize_t output_length16(size_t length);
+ssize_t output_openpgp_header(unsigned char tag, size_t length);
+void output_finish(void);
+void set_binary_mode(FILE *stream);
diff --git a/src/m_paper_key/packets.cpp b/src/m_paper_key/packets.cpp
new file mode 100644
index 0000000..5fd1f04
--- /dev/null
+++ b/src/m_paper_key/packets.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "packets.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "GFSDKBasic.h"
+#include "output.h"
+
+extern int verbose;
+
+auto append_packet(struct packet *packet, unsigned char *buf,
+ size_t len) -> struct packet * {
+ if (packet != nullptr) {
+ while (packet->size - packet->len < len) {
+ packet->size += 100;
+ packet->buf = static_cast<unsigned char *>(
+ GFReallocateMemory(packet->buf, packet->size));
+ }
+
+ memcpy(&packet->buf[packet->len], buf, len);
+ packet->len += len;
+ } else {
+ packet =
+ static_cast<struct packet *>(GFAllocateMemory(sizeof(struct packet)));
+ packet->type = 0;
+ packet->buf = static_cast<unsigned char *>(GFAllocateMemory(len));
+ packet->len = len;
+ packet->size = len;
+
+ memcpy(packet->buf, buf, len);
+ }
+
+ return packet;
+}
+
+void free_packet(struct packet *packet) {
+ if (packet != nullptr) {
+ GFFreeMemory(packet->buf);
+ GFFreeMemory(packet);
+ }
+} \ No newline at end of file
diff --git a/src/m_paper_key/packets.h b/src/m_paper_key/packets.h
new file mode 100644
index 0000000..66e588a
--- /dev/null
+++ b/src/m_paper_key/packets.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include <cstddef>
+
+struct packet {
+ unsigned char type;
+ unsigned char *buf;
+ /* The length the data we've put into buf. */
+ size_t len;
+ /* The length we've malloced for buf. */
+ size_t size;
+};
+
+auto append_packet(struct packet *packet, unsigned char *buf,
+ size_t len) -> struct packet *;
+void free_packet(struct packet *packet);
diff --git a/src/m_paper_key/paperkey.cpp b/src/m_paper_key/paperkey.cpp
new file mode 100644
index 0000000..41cc33b
--- /dev/null
+++ b/src/m_paper_key/paperkey.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007, 2008, 2009, 2012, 2016 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "extract.h"
+#include "output.h"
+
+int verbose = 0, ignore_crc_error = 0;
+unsigned int output_width = 78;
+char *comment = nullptr;
+
+enum options {
+ OPT_HELP = 256,
+ OPT_VERSION,
+ OPT_VERBOSE,
+ OPT_OUTPUT,
+ OPT_INPUT_TYPE,
+ OPT_OUTPUT_TYPE,
+ OPT_OUTPUT_WIDTH,
+ OPT_SECRET_KEY,
+ OPT_PUBRING,
+ OPT_SECRETS,
+ OPT_IGNORE_CRC_ERROR,
+ OPT_FILE_FORMAT,
+ OPT_COMMENT
+};
+
+static struct option long_options[] = {
+ {"help", no_argument, NULL, OPT_HELP},
+ {"version", no_argument, NULL, OPT_VERSION},
+ {"verbose", no_argument, NULL, OPT_VERBOSE},
+ {"output", required_argument, NULL, OPT_OUTPUT},
+ {"input-type", required_argument, NULL, OPT_INPUT_TYPE},
+ {"output-type", required_argument, NULL, OPT_OUTPUT_TYPE},
+ {"output-width", required_argument, NULL, OPT_OUTPUT_WIDTH},
+ {"secret-key", required_argument, NULL, OPT_SECRET_KEY},
+ {"pubring", required_argument, NULL, OPT_PUBRING},
+ {"secrets", required_argument, NULL, OPT_SECRETS},
+ {"ignore-crc-error", no_argument, NULL, OPT_IGNORE_CRC_ERROR},
+ {"file-format", no_argument, NULL, OPT_FILE_FORMAT},
+ {"comment", required_argument, NULL, OPT_COMMENT},
+ {NULL, 0, NULL, 0}};
+
+static void usage(void) {
+ printf("Usage: paperkey [OPTIONS]\n");
+ printf(" --help (-h)\n");
+ printf(" --version (-V)\n");
+ printf(" --verbose (-v) be more verbose\n");
+ printf(" --output (-o) write output to this file\n");
+ printf(" --input-type auto, base16 or raw (binary)\n");
+ printf(" --output-type base16 or raw (binary)\n");
+ printf(" --output-width maximum width of base16 output\n");
+ printf(
+ " --secret-key"
+ " extract secret data from this secret key\n");
+ printf(
+ " --pubring"
+ " public keyring to find non-secret data\n");
+ printf(
+ " --secrets file containing secret"
+ " data to join with the public key\n");
+ printf(" --ignore-crc-error don't reject corrupted input\n");
+ printf(" --file-format show the paperkey file format\n");
+ printf(" --comment add a comment to the base16 output\n");
+}
diff --git a/src/m_paper_key/parse.cpp b/src/m_paper_key/parse.cpp
new file mode 100644
index 0000000..32cb704
--- /dev/null
+++ b/src/m_paper_key/parse.cpp
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2007, 2008, 2012, 2017 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "parse.h"
+
+#include <qcryptographichash.h>
+#include <qdatastream.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include "GFSDKBasic.h"
+#include "output.h"
+#include "packets.h"
+
+extern int verbose;
+extern int ignore_crc_error;
+
+struct packet *parse2(QDataStream &stream, unsigned char want,
+ unsigned char stop) {
+ int byte;
+ struct packet *packet = NULL;
+
+ while (!stream.atEnd()) {
+ stream >> byte;
+
+ unsigned char type;
+ unsigned int length;
+
+ if ((byte & 0x80) != 0) {
+ int tmp;
+
+ type = byte & 0x3F;
+
+ /* Old-style packet type */
+ if (!(byte & 0x40)) type >>= 2;
+
+ if (type == stop) {
+ stream << byte;
+ break;
+ }
+
+ if (byte & 0x40) {
+ /* New-style packets */
+ stream >> byte;
+ if (byte == EOF) goto fail;
+
+ if (byte == 255) {
+ /* 4-byte length */
+ stream >> tmp;
+ if (tmp == EOF) goto fail;
+ length = tmp << 24;
+ stream >> tmp;
+ if (tmp == EOF) goto fail;
+ length |= tmp << 16;
+ stream >> tmp;
+ if (tmp == EOF) goto fail;
+ length |= tmp << 8;
+ stream >> tmp;
+ if (tmp == EOF) goto fail;
+ length |= tmp;
+ } else if (byte >= 224) {
+ /* Partial body length, so fail (keys can't use
+ partial body) */
+ fprintf(stderr, "Invalid partial packet encoding\n");
+ goto fail;
+ } else if (byte >= 192) {
+ /* 2-byte length */
+ stream >> tmp;
+ if (tmp == EOF) goto fail;
+ length = ((byte - 192) << 8) + tmp + 192;
+ } else
+ length = byte;
+ } else {
+ /* Old-style packets */
+ switch (byte & 0x03) {
+ case 0:
+ /* 1-byte length */
+ stream >> byte;
+ if (byte == EOF) goto fail;
+ length = byte;
+ break;
+
+ case 1:
+ /* 2-byte length */
+ stream >> byte;
+ if (byte == EOF) goto fail;
+ stream >> tmp;
+ if (tmp == EOF) goto fail;
+ length = byte << 8;
+ length |= tmp;
+ break;
+
+ case 2:
+ /* 4-byte length */
+ stream >> tmp;
+ if (tmp == EOF) goto fail;
+ length = tmp << 24;
+ stream >> tmp;
+ if (tmp == EOF) goto fail;
+ length |= tmp << 16;
+ stream >> tmp;
+ if (tmp == EOF) goto fail;
+ length |= tmp << 8;
+ stream >> tmp;
+ if (tmp == EOF) goto fail;
+ length |= tmp;
+ break;
+
+ default:
+ fprintf(stderr, "Error: unable to parse old-style length\n");
+ goto fail;
+ }
+ }
+
+ if (verbose > 1)
+ fprintf(stderr, "Found packet of type %d, length %d\n", type, length);
+ } else {
+ fprintf(stderr,
+ "Error: unable to parse OpenPGP packets"
+ " (is this armored data?)\n");
+ goto fail;
+ }
+
+ if (want == 0 || type == want) {
+ packet =
+ static_cast<struct packet *>(GFAllocateMemory(sizeof(struct packet)));
+ packet->type = type;
+ packet->buf = static_cast<unsigned char *>(GFAllocateMemory(length));
+ packet->len = length;
+ packet->size = length;
+ if (stream.readRawData(reinterpret_cast<char *>(packet->buf),
+ packet->len) < packet->len) {
+ fprintf(stderr, "Short read on packet type %d\n", type);
+ goto fail;
+ }
+ break;
+ } else {
+ /* We don't want it, so skip the packet. We don't use fseek
+ here since the input might be on stdin and that isn't
+ seekable. */
+
+ size_t i;
+
+ for (i = 0; i < length; i++) stream >> byte;
+ }
+ }
+
+ return packet;
+
+fail:
+ return nullptr;
+}
+
+struct packet *parse(FILE *input, unsigned char want, unsigned char stop) {
+ int byte;
+ struct packet *packet = NULL;
+
+ while ((byte = fgetc(input)) != EOF) {
+ unsigned char type;
+ unsigned int length;
+
+ if (byte & 0x80) {
+ int tmp;
+
+ type = byte & 0x3F;
+
+ /* Old-style packet type */
+ if (!(byte & 0x40)) type >>= 2;
+
+ if (type == stop) {
+ ungetc(byte, input);
+ break;
+ }
+
+ if (byte & 0x40) {
+ /* New-style packets */
+ byte = fgetc(input);
+ if (byte == EOF) goto fail;
+
+ if (byte == 255) {
+ /* 4-byte length */
+ tmp = fgetc(input);
+ if (tmp == EOF) goto fail;
+ length = tmp << 24;
+ tmp = fgetc(input);
+ if (tmp == EOF) goto fail;
+ length |= tmp << 16;
+ tmp = fgetc(input);
+ if (tmp == EOF) goto fail;
+ length |= tmp << 8;
+ tmp = fgetc(input);
+ if (tmp == EOF) goto fail;
+ length |= tmp;
+ } else if (byte >= 224) {
+ /* Partial body length, so fail (keys can't use
+ partial body) */
+ fprintf(stderr, "Invalid partial packet encoding\n");
+ goto fail;
+ } else if (byte >= 192) {
+ /* 2-byte length */
+ tmp = fgetc(input);
+ if (tmp == EOF) goto fail;
+ length = ((byte - 192) << 8) + tmp + 192;
+ } else
+ length = byte;
+ } else {
+ /* Old-style packets */
+ switch (byte & 0x03) {
+ case 0:
+ /* 1-byte length */
+ byte = fgetc(input);
+ if (byte == EOF) goto fail;
+ length = byte;
+ break;
+
+ case 1:
+ /* 2-byte length */
+ byte = fgetc(input);
+ if (byte == EOF) goto fail;
+ tmp = fgetc(input);
+ if (tmp == EOF) goto fail;
+ length = byte << 8;
+ length |= tmp;
+ break;
+
+ case 2:
+ /* 4-byte length */
+ tmp = fgetc(input);
+ if (tmp == EOF) goto fail;
+ length = tmp << 24;
+ tmp = fgetc(input);
+ if (tmp == EOF) goto fail;
+ length |= tmp << 16;
+ tmp = fgetc(input);
+ if (tmp == EOF) goto fail;
+ length |= tmp << 8;
+ tmp = fgetc(input);
+ if (tmp == EOF) goto fail;
+ length |= tmp;
+ break;
+
+ default:
+ fprintf(stderr, "Error: unable to parse old-style length\n");
+ goto fail;
+ }
+ }
+
+ if (verbose > 1)
+ fprintf(stderr, "Found packet of type %d, length %d\n", type, length);
+ } else {
+ fprintf(stderr,
+ "Error: unable to parse OpenPGP packets"
+ " (is this armored data?)\n");
+ goto fail;
+ }
+
+ if (want == 0 || type == want) {
+ packet =
+ static_cast<struct packet *>(GFAllocateMemory(sizeof(struct packet)));
+ packet->type = type;
+ packet->buf = static_cast<unsigned char *>(GFAllocateMemory(length));
+ packet->len = length;
+ packet->size = length;
+ if (fread(packet->buf, 1, packet->len, input) < packet->len) {
+ fprintf(stderr, "Short read on packet type %d\n", type);
+ goto fail;
+ }
+ break;
+ } else {
+ /* We don't want it, so skip the packet. We don't use fseek
+ here since the input might be on stdin and that isn't
+ seekable. */
+
+ size_t i;
+
+ for (i = 0; i < length; i++) fgetc(input);
+ }
+ }
+
+ return packet;
+
+fail:
+ return NULL;
+}
+
+int calculate_fingerprint(struct packet *packet, size_t public_len,
+ unsigned char fingerprint[20]) {
+ if (packet->buf[0] == 3) {
+ return -1;
+ } else if (packet->buf[0] == 4) {
+ QCryptographicHash sha(QCryptographicHash::Sha1);
+ QByteArray head;
+
+ head.append(static_cast<char>(0x99));
+ head.append(static_cast<char>(public_len >> 8));
+ head.append(static_cast<char>(public_len & 0xFF));
+
+ sha.addData(head);
+ sha.addData(reinterpret_cast<const char *>(packet->buf), public_len);
+ QByteArray result = sha.result();
+
+ if (result.size() != 20) {
+ return -1;
+ }
+
+ std::memcpy(fingerprint, result.constData(), 20);
+ }
+
+ return 0;
+}
+
+#define MPI_LENGTH(_start) (((((_start)[0] << 8 | (_start)[1]) + 7) / 8) + 2)
+
+ssize_t extract_secrets(struct packet *packet) {
+ size_t offset;
+
+ if (packet->len == 0) return -1;
+
+ /* Secret keys consist of a public key with some secret material
+ stuck on the end. To get to the secrets, we have to skip the
+ public stuff. */
+
+ if (packet->buf[0] == 3) {
+ fprintf(stderr, "Version 3 (PGP 2.x style) keys are not supported.\n");
+ return -1;
+ } else if (packet->buf[0] == 4) {
+ /* Jump 5 bytes in. That gets us past 1 byte of version, and 4
+ bytes of timestamp. */
+
+ offset = 5;
+ } else
+ return -1;
+
+ if (packet->len <= offset) return -1;
+
+ switch (packet->buf[offset++]) {
+ case 1: /* RSA */
+ /* Skip 2 MPIs */
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ break;
+
+ case 16: /* Elgamal */
+ /* Skip 3 MPIs */
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ break;
+
+ case 17: /* DSA */
+ /* Skip 4 MPIs */
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ break;
+
+ case 18: /* ECDH */
+ /* Skip the curve ID and its length byte, plus an MPI, plus the
+ KDF parameters and their length byte */
+ offset += packet->buf[offset] + 1;
+ if (packet->len <= offset) return -1;
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ offset += packet->buf[offset] + 1;
+ if (packet->len <= offset) return -1;
+ break;
+
+ case 19: /* ECDSA */
+ case 22: /* EdDSA - note that this is from an expired draft
+ https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-04,
+ but GnuPG is using algorithm 22 for it. */
+ /* Skip the curve ID and its length byte, plus an MPI */
+ offset += packet->buf[offset] + 1;
+ if (packet->len <= offset) return -1;
+ offset += MPI_LENGTH(&packet->buf[offset]);
+ if (packet->len <= offset) return -1;
+ break;
+
+ default:
+ /* What algorithm? */
+ fprintf(stderr, "Unable to parse algorithm %u\n",
+ packet->buf[offset - 1]);
+ return -1;
+ }
+
+ return offset;
+}
+
+struct packet *read_secrets_file(FILE *secrets, enum data_type input_type) {
+ struct packet *packet = NULL;
+ int final_crc = 0;
+ unsigned long my_crc = 0;
+
+ if (input_type == RAW) {
+ unsigned char buffer[1024];
+ size_t got;
+
+ while ((got = fread(buffer, 1, 1024, secrets)))
+ packet = append_packet(packet, buffer, got);
+
+ if (got == 0 && !feof(secrets)) {
+ fprintf(stderr, "Error: unable to read secrets file\n");
+ free_packet(packet);
+ return NULL;
+ }
+
+ if (packet->len >= 3) {
+ /* Grab the last 3 bytes to be the CRC24 */
+ my_crc = packet->buf[packet->len - 3] << 16;
+ my_crc |= packet->buf[packet->len - 2] << 8;
+ my_crc |= packet->buf[packet->len - 1];
+ final_crc = 1;
+ packet->len -= 3;
+ }
+ } else {
+ char line[1024];
+ unsigned int next_linenum = 1;
+
+ while (fgets(line, 1024, secrets)) {
+ unsigned int linenum, did_digit = 0;
+ unsigned long line_crc = CRC24_INIT;
+ char *tok;
+
+ if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') continue;
+
+ linenum = atoi(line);
+ if (linenum != next_linenum) {
+ fprintf(stderr, "Error: missing line number %u (saw %u)\n",
+ next_linenum, linenum);
+ free_packet(packet);
+ return NULL;
+ } else
+ next_linenum = linenum + 1;
+
+ tok = strchr(line, ':');
+ if (tok) {
+ tok = strchr(tok, ' ');
+
+ while (tok) {
+ char *next;
+
+ while (*tok == ' ') tok++;
+
+ next = strchr(tok, ' ');
+
+ if (next == NULL) {
+ /* End of line, so check the CRC. */
+ unsigned long new_crc;
+
+ if (sscanf(tok, "%06lX", &new_crc)) {
+ if (did_digit) {
+ if ((new_crc & 0xFFFFFFL) != (line_crc & 0xFFFFFFL)) {
+ fprintf(stderr,
+ "CRC on line %d does not"
+ " match (%06lX!=%06lX)\n",
+ linenum, new_crc & 0xFFFFFFL, line_crc & 0xFFFFFFL);
+ if (!ignore_crc_error) {
+ free_packet(packet);
+ return NULL;
+ }
+ }
+ } else {
+ final_crc = 1;
+ my_crc = new_crc;
+ }
+ }
+ } else {
+ unsigned int digit;
+
+ if (sscanf(tok, "%02X", &digit)) {
+ unsigned char d = digit;
+ packet = append_packet(packet, &d, 1);
+ do_crc24(&line_crc, &d, 1);
+ did_digit = 1;
+ }
+ }
+
+ tok = next;
+ }
+ } else {
+ fprintf(stderr, "No colon ':' found in line %u\n", linenum);
+ free_packet(packet);
+ return NULL;
+ }
+ }
+ }
+
+ if (final_crc) {
+ unsigned long all_crc = CRC24_INIT;
+
+ do_crc24(&all_crc, packet->buf, packet->len);
+
+ if ((my_crc & 0xFFFFFFL) != (all_crc & 0xFFFFFFL)) {
+ fprintf(stderr, "CRC of secret does not match (%06lX!=%06lX)\n",
+ my_crc & 0xFFFFFFL, all_crc & 0xFFFFFFL);
+ if (!ignore_crc_error) {
+ free_packet(packet);
+ return NULL;
+ }
+ }
+ } else {
+ fprintf(stderr, "CRC of secret is missing\n");
+ if (!ignore_crc_error) {
+ free_packet(packet);
+ return NULL;
+ }
+ }
+
+ return packet;
+} \ No newline at end of file
diff --git a/src/m_paper_key/parse.h b/src/m_paper_key/parse.h
new file mode 100644
index 0000000..4719772
--- /dev/null
+++ b/src/m_paper_key/parse.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include <qstring.h>
+
+#include "output.h"
+
+auto parse(FILE *input, unsigned char want,
+ unsigned char stop) -> struct packet *;
+
+struct packet *parse2(QDataStream &stream, unsigned char want,
+ unsigned char stop);
+
+auto calculate_fingerprint(struct packet *packet, size_t public_len,
+ unsigned char fingerprint[20]) -> int;
+
+auto extract_secrets(struct packet *packet) -> ssize_t;
+
+auto read_secrets_file(FILE *secrets,
+ enum data_type input_type) -> struct packet *;
diff --git a/src/m_paper_key/restore.cpp b/src/m_paper_key/restore.cpp
new file mode 100644
index 0000000..e1c8146
--- /dev/null
+++ b/src/m_paper_key/restore.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2007, 2012 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "restore.h"
+
+#include <cctype>
+#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include "GFSDKBasic.h"
+#include "output.h"
+#include "packets.h"
+#include "parse.h"
+
+struct key {
+ unsigned char fpr[20];
+ struct packet *packet;
+ struct key *next;
+};
+
+static auto extract_keys(struct packet *packet) -> struct key * {
+ struct key *key = NULL;
+ size_t idx = 1;
+
+ /* Check the version */
+ if (packet->len && packet->buf[0] != 0) {
+ fprintf(stderr, "Cannot handle secrets file version %d\n", packet->buf[0]);
+ return NULL;
+ }
+
+ while (idx < packet->len) {
+ /* 1+20+2 == version + fingerprint + length */
+ if (idx + 1 + 20 + 2 <= packet->len) {
+ if (packet->buf[idx] == 4) {
+ unsigned int len;
+ struct key *newkey;
+
+ newkey =
+ static_cast<struct key *>(GFAllocateMemory(sizeof(struct key)));
+ newkey->next = NULL;
+
+ idx++;
+ memcpy(newkey->fpr, &packet->buf[idx], 20);
+
+ idx += 20;
+
+ len = packet->buf[idx++] << 8;
+ len |= packet->buf[idx++];
+
+ if (idx + len <= packet->len) {
+ newkey->packet = append_packet(NULL, &packet->buf[idx], len);
+ idx += len;
+ } else {
+ fprintf(stderr, "Warning: Short data in secret image\n");
+ free(newkey);
+ break;
+ }
+
+ newkey->next = key;
+ key = newkey;
+ } else {
+ fprintf(stderr, "Warning: Corrupt data in secret image\n");
+ break;
+ }
+ } else {
+ fprintf(stderr, "Warning: Short header in secret image\n");
+ break;
+ }
+ }
+
+ return key;
+}
+
+static void free_keys(struct key *key) {
+ while (key) {
+ struct key *keytmp = key;
+ free_packet(key->packet);
+ key = key->next;
+ free(keytmp);
+ }
+}
+
+auto restore(FILE *pubring, FILE *secrets, enum data_type input_type,
+ const char *outname) -> int {
+ struct packet *secret;
+
+ if (input_type == AUTO) {
+ int test = fgetc(secrets);
+
+ if (test == EOF) {
+ fprintf(stderr, "Unable to check type of secrets file\n");
+ return 1;
+ } else if (isascii(test) && isprint(test))
+ input_type = BASE16;
+ else
+ input_type = RAW;
+
+ ungetc(test, secrets);
+ }
+
+ secret = read_secrets_file(secrets, input_type);
+ if (secret) {
+ struct packet *pubkey;
+ struct key *keys;
+ int did_pubkey = 0;
+
+ /* Build a list of all keys. We need to do this since the
+ public key we are transforming might have the subkeys in a
+ different order than (or not match subkeys at all with) our
+ secret data. */
+
+ keys = extract_keys(secret);
+ if (keys) {
+ output_start(outname, RAW, NULL);
+
+ while ((pubkey = parse(pubring, 0, 0))) {
+ unsigned char ptag;
+
+ if (pubkey->type == 6 || pubkey->type == 14) {
+ /* Public key or subkey */
+ unsigned char fpr[20];
+ struct key *keyidx;
+
+ if (pubkey->type == 6 && did_pubkey) break;
+
+ calculate_fingerprint(pubkey, pubkey->len, fpr);
+
+ /* Do we have a secret key that matches? */
+ for (keyidx = keys; keyidx; keyidx = keyidx->next) {
+ if (memcmp(fpr, keyidx->fpr, 20) == 0) {
+ if (pubkey->type == 6) {
+ ptag = 5;
+ did_pubkey = 1;
+ } else
+ ptag = 7;
+
+ /* Match, so create a secret key. */
+ output_openpgp_header(ptag, pubkey->len + keyidx->packet->len);
+ output_packet(pubkey);
+ output_packet(keyidx->packet);
+ }
+ }
+ } else if (did_pubkey) {
+ /* Copy the usual user ID, sigs, etc, so the key is
+ well-formed. */
+ output_openpgp_header(pubkey->type, pubkey->len);
+ output_packet(pubkey);
+ }
+
+ free_packet(pubkey);
+ }
+
+ free_keys(keys);
+ } else {
+ fprintf(stderr, "Unable to parse secret data\n");
+ return 1;
+ }
+ } else {
+ fprintf(stderr, "Unable to read secrets file\n");
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/src/m_paper_key/restore.h b/src/m_paper_key/restore.h
new file mode 100644
index 0000000..7b908ef
--- /dev/null
+++ b/src/m_paper_key/restore.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2007 David Shaw <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _RESTORE_H_
+#define _RESTORE_H_
+
+#include "output.h"
+
+auto restore(FILE *pubring, FILE *secrets, enum data_type input_type,
+ const char *outname) -> int;
+
+#endif /* !_RESTORE_H_ */
diff --git a/src/m_pinentry/CMakeLists.txt b/src/m_pinentry/CMakeLists.txt
new file mode 100644
index 0000000..c8b8473
--- /dev/null
+++ b/src/m_pinentry/CMakeLists.txt
@@ -0,0 +1,63 @@
+# Copyright (C) 2021 Saturneric <[email protected]>
+#
+# This file is part of GpgFrontend.
+#
+# GpgFrontend 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.
+#
+# GpgFrontend 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+#
+# The initial version of the source code is inherited from
+# the gpg4usb project, which is under GPL-3.0-or-later.
+#
+# All the source code of GpgFrontend was modified and released by
+# Saturneric <[email protected]> starting on May 12, 2021.
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+aux_source_directory(. INTEGRATED_MODULE_SOURCE)
+
+# capslock
+list(APPEND INTEGRATED_MODULE_SOURCE "capslock/capslock.cpp")
+if (MINGW)
+ list(APPEND INTEGRATED_MODULE_SOURCE "capslock/capslock_win.cpp")
+else()
+ list(APPEND INTEGRATED_MODULE_SOURCE "capslock/capslock_unix.cpp")
+endif()
+
+list(APPEND INTEGRATED_MODULE_SOURCE "pinentry.qrc")
+
+# define module
+add_library(mod_pinentry SHARED ${INTEGRATED_MODULE_SOURCE})
+
+# install dir
+install(TARGETS mod_pinentry
+ LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/modules")
+
+
+# link options
+
+# link sdk
+target_link_libraries(mod_pinentry PRIVATE
+ gpgfrontend_module_sdk)
+
+if(GPGFRONTEND_QT5_BUILD)
+ # link Qt core
+ target_link_libraries(mod_pinentry PUBLIC Qt5::Widgets)
+else()
+ # link Qt core
+ target_link_libraries(mod_pinentry PUBLIC Qt6::Widgets)
+endif()
+
+
+# using std c++ 17
+target_compile_features(mod_pinentry PUBLIC cxx_std_17)
+
diff --git a/src/m_pinentry/GpgPassphraseContext.cpp b/src/m_pinentry/GpgPassphraseContext.cpp
new file mode 100644
index 0000000..c96f29a
--- /dev/null
+++ b/src/m_pinentry/GpgPassphraseContext.cpp
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2021 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend 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.
+ *
+ * GpgFrontend 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#include "GpgPassphraseContext.h"
+
+GpgPassphraseContext::GpgPassphraseContext(const QString& uids_info,
+ const QString& passphrase_info,
+ bool prev_was_bad, bool ask_for_new)
+ : passphrase_info_(passphrase_info),
+ uids_info_(uids_info),
+ prev_was_bad_(prev_was_bad),
+ ask_for_new_(ask_for_new) {}
+
+GpgPassphraseContext::GpgPassphraseContext() = default;
+
+auto GpgPassphraseContext::GetPassphrase() const -> QString {
+ return passphrase_;
+}
+
+void GpgPassphraseContext::SetPassphrase(const QString& passphrase) {
+ passphrase_ = passphrase;
+}
+
+auto GpgPassphraseContext::GetUidsInfo() const -> QString { return uids_info_; }
+
+auto GpgPassphraseContext::GetPassphraseInfo() const -> QString {
+ return passphrase_info_;
+}
+
+auto GpgPassphraseContext::IsPreWasBad() const -> bool { return prev_was_bad_; }
+
+auto GpgPassphraseContext::IsAskForNew() const -> bool { return ask_for_new_; } \ No newline at end of file
diff --git a/src/m_pinentry/GpgPassphraseContext.h b/src/m_pinentry/GpgPassphraseContext.h
new file mode 100644
index 0000000..4060d36
--- /dev/null
+++ b/src/m_pinentry/GpgPassphraseContext.h
@@ -0,0 +1,61 @@
+
+
+/**
+ * Copyright (C) 2021 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend 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.
+ *
+ * GpgFrontend 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <QObject>
+
+class GpgPassphraseContext : public QObject {
+ Q_OBJECT
+ public:
+ GpgPassphraseContext(const QString& uids_info, const QString& passphrase_info,
+ bool prev_was_bad, bool ask_for_new);
+
+ GpgPassphraseContext();
+
+ void SetPassphrase(const QString& passphrase);
+
+ [[nodiscard]] auto GetPassphrase() const -> QString;
+
+ [[nodiscard]] auto GetUidsInfo() const -> QString;
+
+ [[nodiscard]] auto GetPassphraseInfo() const -> QString;
+
+ [[nodiscard]] auto IsPreWasBad() const -> bool;
+
+ [[nodiscard]] auto IsAskForNew() const -> bool;
+
+ private:
+ QString passphrase_info_;
+ QString uids_info_;
+ QString passphrase_;
+ bool prev_was_bad_;
+ bool ask_for_new_;
+};
diff --git a/src/m_pinentry/PinentryModule.cpp b/src/m_pinentry/PinentryModule.cpp
new file mode 100644
index 0000000..da022c4
--- /dev/null
+++ b/src/m_pinentry/PinentryModule.cpp
@@ -0,0 +1,106 @@
+/**
+ * Copyright (C) 2021 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend 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.
+ *
+ * GpgFrontend 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#include "PinentryModule.h"
+
+#include <qapplication.h>
+#include <qobjectdefs.h>
+#include <qthread.h>
+
+#include "GFModuleCommonUtils.hpp"
+#include "GFSDKBuildInfo.h"
+#include "GpgPassphraseContext.h"
+#include "RaisePinentry.h"
+
+auto GFGetModuleGFSDKVersion() -> const char * {
+ return DUP(GF_SDK_VERSION_STR);
+}
+
+auto GFGetModuleQtEnvVersion() -> const char * { return DUP(QT_VERSION_STR); }
+
+auto GFGetModuleID() -> const char * {
+ return DUP("com.bktus.gpgfrontend.module.pinentry");
+}
+
+auto GFGetModuleVersion() -> const char * { return DUP("1.0.0"); }
+
+auto GFGetModuleMetaData() -> GFModuleMetaData * {
+ return QMapToGFModuleMetaDataList({{"Name", "Pinentry"},
+ {"Description", "A simple tiny pinentry."},
+ {"Author", "Saturneric"}});
+}
+
+auto GFRegisterModule() -> int {
+ MLogDebug("pinentry module registering");
+
+ return 0;
+}
+
+auto GFActiveModule() -> int {
+ MLogDebug("pinentry module activating");
+
+ GFModuleListenEvent(GFGetModuleID(), DUP("REQUEST_PIN_ENTRY"));
+ return 0;
+}
+
+auto GFExecuteModule(GFModuleEvent *p_event) -> int {
+ MLogDebug(
+ QString("pinentry module executing, event id: %1").arg(p_event->id));
+
+ auto event = ConvertEventToMap(p_event);
+
+ QMetaObject::invokeMethod(
+ QApplication::instance()->thread(), [p_event, event]() -> int {
+ auto *p = new RaisePinentry(
+ nullptr,
+ SecureCreateQSharedObject<GpgPassphraseContext>(
+ event["uid_hint"], event["passphrase_info"],
+ event["prev_was_bad"].toInt(), event["ask_for_new"].toInt()));
+
+ QObject::connect(
+ p, &RaisePinentry::SignalUserInputPassphraseCallback, p,
+ [event](const QSharedPointer<GpgPassphraseContext> &c) {
+ GFModuleTriggerModuleEventCallback(
+ ConvertMapToEvent(event), GFGetModuleID(), 1,
+ ConvertMapToParams({{"passphrase", c->GetPassphrase()}}));
+ });
+
+ p->Exec();
+ return 0;
+ });
+
+ return 0;
+}
+
+auto GFDeactiveModule() -> int { return 0; }
+
+auto GFUnregisterModule() -> int {
+ MLogDebug("pinentry module unregistering");
+
+ return 0;
+} \ No newline at end of file
diff --git a/src/m_pinentry/PinentryModule.h b/src/m_pinentry/PinentryModule.h
new file mode 100644
index 0000000..b8b73d0
--- /dev/null
+++ b/src/m_pinentry/PinentryModule.h
@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 2021 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend 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.
+ *
+ * GpgFrontend 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <GFSDKModule.h>
+
+#include "GFModuleExport.h"
+
+extern "C" {
+
+auto GF_MODULE_EXPORT GFGetModuleGFSDKVersion() -> const char *;
+
+auto GF_MODULE_EXPORT GFGetModuleQtEnvVersion() -> const char *;
+
+auto GF_MODULE_EXPORT GFGetModuleID() -> const char *;
+
+auto GF_MODULE_EXPORT GFGetModuleVersion() -> const char *;
+
+auto GF_MODULE_EXPORT GFGetModuleMetaData() -> GFModuleMetaData *;
+
+auto GF_MODULE_EXPORT GFRegisterModule() -> int;
+
+auto GF_MODULE_EXPORT GFActiveModule() -> int;
+
+auto GF_MODULE_EXPORT GFExecuteModule(GFModuleEvent *) -> int;
+
+auto GF_MODULE_EXPORT GFDeactiveModule() -> int;
+
+auto GF_MODULE_EXPORT GFUnregisterModule() -> int;
+}; \ No newline at end of file
diff --git a/src/m_pinentry/RaisePinentry.cpp b/src/m_pinentry/RaisePinentry.cpp
new file mode 100644
index 0000000..283bd81
--- /dev/null
+++ b/src/m_pinentry/RaisePinentry.cpp
@@ -0,0 +1,107 @@
+/**
+ * Copyright (C) 2021 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend 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.
+ *
+ * GpgFrontend 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#include "RaisePinentry.h"
+
+#include <qapplication.h>
+
+#include "GpgPassphraseContext.h"
+#include "pinentrydialog.h"
+
+auto FindTopMostWindow(QWidget* fallback) -> QWidget* {
+ QList<QWidget*> top_widgets = QApplication::topLevelWidgets();
+ foreach (QWidget* widget, top_widgets) {
+ if (widget->isActiveWindow()) {
+ return widget;
+ }
+ }
+ return fallback;
+}
+
+RaisePinentry::RaisePinentry(QWidget* parent,
+ QSharedPointer<GpgPassphraseContext> context)
+ : QWidget(parent), context_(std::move(context)) {}
+
+auto RaisePinentry::Exec() -> int {
+ bool ask_for_new = context_->IsAskForNew() &&
+ context_->GetPassphraseInfo().isEmpty() &&
+ context_->GetUidsInfo().isEmpty();
+
+ auto* pinentry =
+ new PinEntryDialog(FindTopMostWindow(this), 0, 15, true, ask_for_new,
+ ask_for_new ? tr("Repeat Passphrase:") : QString(),
+ tr("Show passphrase"), tr("Hide passphrase"));
+
+ if (context_->IsPreWasBad()) {
+ pinentry->setError(tr("Given Passphrase was wrong. Please retry."));
+ }
+
+ pinentry->setPrompt(tr("Passphrase:"));
+
+ if (!context_->GetUidsInfo().isEmpty()) {
+ pinentry->setDescription(QString("Please provide Passphrase of Key:\n%1\n")
+ .arg(context_->GetUidsInfo()));
+ }
+
+ struct pinentry pinentry_info;
+ pinentry->setPinentryInfo(pinentry_info);
+
+ pinentry->setRepeatErrorText(tr("Passphrases do not match"));
+ pinentry->setGenpinLabel(QString(""));
+ pinentry->setGenpinTT(QString(""));
+ pinentry->setCapsLockHint(tr("Caps Lock is on"));
+ pinentry->setFormattedPassphrase({false, QString()});
+ pinentry->setConstraintsOptions({false, QString(), QString(), QString()});
+
+ pinentry->setWindowTitle(tr("Bundled Pinentry"));
+
+ /* If we reuse the same dialog window. */
+ pinentry->setPin(QString());
+ pinentry->setOkText(tr("Confirm"));
+ pinentry->setCancelText(tr("Cancel"));
+
+ connect(pinentry, &PinEntryDialog::finished, this,
+ [pinentry, this](int result) {
+ bool ret = result != 0;
+
+ if (!ret) {
+ emit SignalUserInputPassphraseCallback({});
+ return -1;
+ }
+
+ auto pin = pinentry->pin().toUtf8();
+
+ context_->SetPassphrase(pin);
+ emit SignalUserInputPassphraseCallback(context_);
+ return 0;
+ });
+ connect(pinentry, &PinEntryDialog::finished, this, &QWidget::deleteLater);
+
+ pinentry->open();
+ return 0;
+}
diff --git a/src/m_pinentry/RaisePinentry.h b/src/m_pinentry/RaisePinentry.h
new file mode 100644
index 0000000..4c8ace6
--- /dev/null
+++ b/src/m_pinentry/RaisePinentry.h
@@ -0,0 +1,58 @@
+/**
+ * Copyright (C) 2021 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend 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.
+ *
+ * GpgFrontend 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <QWidget>
+
+class GpgPassphraseContext;
+
+class RaisePinentry : public QWidget {
+ Q_OBJECT
+ public:
+ /**
+ * @brief Construct a new Raise Pinentry object
+ *
+ * @param parent
+ */
+ explicit RaisePinentry(QWidget *parent, QSharedPointer<GpgPassphraseContext>);
+
+ /**
+ * @brief
+ *
+ * @return int
+ */
+ auto Exec() -> int;
+
+ signals:
+
+ void SignalUserInputPassphraseCallback(QSharedPointer<GpgPassphraseContext>);
+
+ private:
+ QSharedPointer<GpgPassphraseContext> context_;
+};
diff --git a/src/m_pinentry/accessibility.cpp b/src/m_pinentry/accessibility.cpp
new file mode 100644
index 0000000..f139832
--- /dev/null
+++ b/src/m_pinentry/accessibility.cpp
@@ -0,0 +1,44 @@
+/* accessibility.cpp - Helpers for making pinentry accessible
+ * Copyright (C) 2021 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "accessibility.h"
+
+#include <QString>
+#include <QWidget>
+
+namespace Accessibility {
+
+void setDescription(QWidget *w, const QString &text) {
+ if (w) {
+#ifndef QT_NO_ACCESSIBILITY
+ w->setAccessibleDescription(text);
+#endif
+ }
+}
+
+void setName(QWidget *w, const QString &text) {
+ if (w) {
+#ifndef QT_NO_ACCESSIBILITY
+ w->setAccessibleName(text);
+#endif
+ }
+}
+
+} // namespace Accessibility
diff --git a/src/m_pinentry/accessibility.h b/src/m_pinentry/accessibility.h
new file mode 100644
index 0000000..9ef912d
--- /dev/null
+++ b/src/m_pinentry/accessibility.h
@@ -0,0 +1,40 @@
+/* accessibility.h - Helpers for making pinentry accessible
+ * Copyright (C) 2021 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef __PINENTRY_QT_ACCESSIBILITY_H__
+#define __PINENTRY_QT_ACCESSIBILITY_H__
+
+class QString;
+class QWidget;
+
+namespace Accessibility
+{
+
+/* Wrapper for QWidget::setAccessibleDescription which does nothing if
+ QT_NO_ACCESSIBILITY is defined. */
+void setDescription(QWidget *w, const QString &text);
+
+/* Wrapper for QWidget::setAccessibleName which does nothing if
+ QT_NO_ACCESSIBILITY is defined. */
+void setName(QWidget *w, const QString &text);
+
+} // namespace Accessibility
+
+#endif // __PINENTRY_QT_ACCESSIBILITY_H__
diff --git a/src/m_pinentry/capslock/capslock.cpp b/src/m_pinentry/capslock/capslock.cpp
new file mode 100644
index 0000000..a730c22
--- /dev/null
+++ b/src/m_pinentry/capslock/capslock.cpp
@@ -0,0 +1,41 @@
+/* capslock.cpp - Helper to check whether Caps Lock is on
+ * Copyright (C) 2021 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <QDebug>
+#include <QGuiApplication>
+
+#include "capslock.h"
+
+CapsLockWatcher::Private::Private(CapsLockWatcher *q) : q{q} {
+#ifdef PINENTRY_QT_WAYLAND
+ if (qApp->platformName() == QLatin1String("wayland")) {
+ watchWayland();
+ }
+#endif
+}
+
+CapsLockWatcher::CapsLockWatcher(QObject *parent)
+ : QObject{parent}, d{new Private{this}} {
+ if (qApp->platformName() == QLatin1String("wayland")) {
+#ifndef PINENTRY_QT_WAYLAND
+ qWarning() << "CapsLockWatcher was compiled without support for Wayland";
+#endif
+ }
+}
diff --git a/src/m_pinentry/capslock/capslock.h b/src/m_pinentry/capslock/capslock.h
new file mode 100644
index 0000000..138f88c
--- /dev/null
+++ b/src/m_pinentry/capslock/capslock.h
@@ -0,0 +1,77 @@
+/* capslock.h - Helper to check whether Caps Lock is on
+ * Copyright (C) 2021 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef __PINENTRY_QT_CAPSLOCK_H__
+#define __PINENTRY_QT_CAPSLOCK_H__
+
+#include <QObject>
+#include <memory>
+
+enum class LockState { Unknown = -1, Off, On };
+
+LockState capsLockState();
+
+#ifdef PINENTRY_QT_WAYLAND
+namespace KWayland {
+namespace Client {
+class Registry;
+class Seat;
+} // namespace Client
+} // namespace KWayland
+#endif
+
+class CapsLockWatcher : public QObject {
+ Q_OBJECT
+
+ public:
+ explicit CapsLockWatcher(QObject *parent = nullptr);
+
+ Q_SIGNALS:
+ void stateChanged(bool locked);
+
+ private:
+ class Private;
+ std::unique_ptr<Private> d;
+};
+
+class CapsLockWatcher::Private {
+ public:
+ explicit Private(CapsLockWatcher *);
+#ifdef PINENTRY_QT_WAYLAND
+ void watchWayland();
+#endif
+
+ private:
+#ifdef PINENTRY_QT_WAYLAND
+ void registry_seatAnnounced(quint32, quint32);
+ void seat_hasKeyboardChanged(bool);
+ void keyboard_modifiersChanged(quint32);
+#endif
+
+ private:
+ CapsLockWatcher *const q;
+
+#ifdef PINENTRY_QT_WAYLAND
+ KWayland::Client::Registry *registry = nullptr;
+ KWayland::Client::Seat *seat = nullptr;
+#endif
+};
+
+#endif // __PINENTRY_QT_CAPSLOCK_H__
diff --git a/src/m_pinentry/capslock/capslock_unix.cpp b/src/m_pinentry/capslock/capslock_unix.cpp
new file mode 100644
index 0000000..e4f4cd1
--- /dev/null
+++ b/src/m_pinentry/capslock/capslock_unix.cpp
@@ -0,0 +1,137 @@
+/* capslock_unix.cpp - Helper to check whether Caps Lock is on
+ * Copyright (C) 2021 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "capslock.h"
+
+#ifdef PINENTRY_QT_WAYLAND
+#include <KWayland/Client/connection_thread.h>
+#include <KWayland/Client/keyboard.h>
+#include <KWayland/Client/registry.h>
+#include <KWayland/Client/seat.h>
+#endif
+
+#include <QGuiApplication>
+
+#ifdef PINENTRY_QT_X11
+#include <X11/XKBlib.h>
+
+#include <QX11Info>
+#undef Status
+#endif
+
+#include <QDebug>
+
+#ifdef PINENTRY_QT_WAYLAND
+using namespace KWayland::Client;
+#endif
+
+#ifdef PINENTRY_QT_WAYLAND
+static bool watchingWayland = false;
+#endif
+
+LockState capsLockState() {
+ static bool reportUnsupportedPlatform = true;
+#ifdef PINENTRY_QT_X11
+ if (qApp->platformName() == QLatin1String("xcb")) {
+ unsigned int state;
+ XkbGetIndicatorState(QX11Info::display(), XkbUseCoreKbd, &state);
+ return (state & 0x01) == 1 ? LockState::On : LockState::Off;
+ }
+#endif
+#ifdef PINENTRY_QT_WAYLAND
+ if (qApp->platformName() == QLatin1String("wayland")) {
+ if (!watchingWayland && reportUnsupportedPlatform) {
+ qDebug() << "Use CapsLockWatcher for checking for Caps Lock on Wayland";
+ }
+ } else
+#endif
+ if (reportUnsupportedPlatform) {
+ qWarning() << "Checking for Caps Lock not possible on unsupported platform:"
+ << qApp->platformName();
+ }
+ reportUnsupportedPlatform = false;
+ return LockState::Unknown;
+}
+
+#ifdef PINENTRY_QT_WAYLAND
+void CapsLockWatcher::Private::watchWayland() {
+ watchingWayland = true;
+ auto connection = ConnectionThread::fromApplication(q);
+ if (!connection) {
+ qWarning() << "Failed to get connection to Wayland server from QPA";
+ return;
+ }
+ registry = new Registry{q};
+ registry->create(connection);
+ if (!registry->isValid()) {
+ qWarning() << "Failed to create valid KWayland registry";
+ return;
+ }
+ registry->setup();
+
+ connect(registry, &Registry::seatAnnounced, q,
+ [this](quint32 name, quint32 version) {
+ registry_seatAnnounced(name, version);
+ });
+}
+
+void CapsLockWatcher::Private::registry_seatAnnounced(quint32 name,
+ quint32 version) {
+ Q_ASSERT(registry);
+ seat = registry->createSeat(name, version, q);
+ if (!seat->isValid()) {
+ qWarning() << "Failed to create valid KWayland seat";
+ return;
+ }
+
+ connect(seat, &Seat::hasKeyboardChanged, q,
+ [this](bool hasKeyboard) { seat_hasKeyboardChanged(hasKeyboard); });
+}
+
+void CapsLockWatcher::Private::seat_hasKeyboardChanged(bool hasKeyboard) {
+ Q_ASSERT(seat);
+
+ if (!hasKeyboard) {
+ qDebug() << "Seat has no keyboard";
+ return;
+ }
+
+ auto keyboard = seat->createKeyboard(q);
+ if (!keyboard->isValid()) {
+ qWarning() << "Failed to create valid KWayland keyboard";
+ return;
+ }
+
+ connect(keyboard, &Keyboard::modifiersChanged, q,
+ [this](quint32, quint32, quint32 locked, quint32) {
+ keyboard_modifiersChanged(locked);
+ });
+}
+
+void CapsLockWatcher::Private::keyboard_modifiersChanged(quint32 locked) {
+ const bool capsLockIsLocked = (locked & 2u) != 0;
+ qDebug() << "Caps Lock is locked:" << capsLockIsLocked;
+ Q_EMIT q->stateChanged(capsLockIsLocked);
+}
+#endif
diff --git a/src/m_pinentry/capslock/capslock_win.cpp b/src/m_pinentry/capslock/capslock_win.cpp
new file mode 100644
index 0000000..46bc704
--- /dev/null
+++ b/src/m_pinentry/capslock/capslock_win.cpp
@@ -0,0 +1,26 @@
+/* capslock_win.cpp - Helper to check whether Caps Lock is on
+ * Copyright (C) 2021 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+#include <windows.h>
+
+#include "capslock.h"
+
+LockState capsLockState() {
+ return (GetKeyState(VK_CAPITAL) & 1) ? LockState::On : LockState::Off;
+}
diff --git a/src/m_pinentry/icons/data-error.svg b/src/m_pinentry/icons/data-error.svg
new file mode 100644
index 0000000..6fc3137
--- /dev/null
+++ b/src/m_pinentry/icons/data-error.svg
@@ -0,0 +1,9 @@
+<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-NegativeText {
+ color:#da4453;
+ }
+ </style>
+ <rect class="ColorScheme-NegativeText" x="3" y="3" width="16" height="16" rx="2" fill="currentColor"/>
+ <path d="M 6.414,5 5,6.414 9.586,11 5,15.586 6.414,17 11,12.414 15.586,17 17,15.586 12.414,11 17,6.414 15.586,5 11,9.586 Z" fill="#fff"/>
+</svg>
diff --git a/src/m_pinentry/icons/document-encrypt.png b/src/m_pinentry/icons/document-encrypt.png
new file mode 100644
index 0000000..b80c2a6
--- /dev/null
+++ b/src/m_pinentry/icons/document-encrypt.png
Binary files differ
diff --git a/src/m_pinentry/icons/hint.svg b/src/m_pinentry/icons/hint.svg
new file mode 100644
index 0000000..f6b818f
--- /dev/null
+++ b/src/m_pinentry/icons/hint.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 13.314453 2 L 2 13.294922 L 2.7148438 14 L 14 2.6972656 L 13.314453 2 z M 8 3 A 8.9999916 9.000003 0 0 0 0.12304688 7.6679688 C 0.25199187 8.0317035 0.48048562 8.3445563 0.77929688 8.5761719 A 7.9999926 8.0000028 0 0 1 8 4 A 3.9999993 4.0000007 0 0 0 4 8 A 3.9999993 4.0000007 0 0 0 4.1054688 8.8945312 L 5 8 A 2.9999993 3.0000005 0 0 1 8 5 L 8.8925781 4.1074219 A 3.9999993 4.0000007 0 0 0 8.3496094 4.0175781 A 7.9999926 8.0000028 0 0 1 8.9277344 4.0722656 L 9.8066406 3.1933594 A 8.9999916 9.000003 0 0 0 8 3 z M 13.835938 5.1640625 L 13.121094 5.8789062 A 7.9999926 8.0000028 0 0 1 15.220703 8.5761719 C 15.522218 8.3424607 15.752612 8.0261216 15.880859 7.6582031 A 8.9999916 9.000003 0 0 0 13.835938 5.1640625 z M 11.894531 7.1054688 L 11 8 A 2.9999993 3.0000005 0 0 1 8 11 L 7.1074219 11.892578 A 3.9999993 4.0000007 0 0 0 8 12 A 3.9999993 4.0000007 0 0 0 12 8 A 3.9999993 4.0000007 0 0 0 11.894531 7.1054688 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/src/m_pinentry/icons/password-generate.svg b/src/m_pinentry/icons/password-generate.svg
new file mode 100644
index 0000000..12d703c
--- /dev/null
+++ b/src/m_pinentry/icons/password-generate.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="m3.5 2l-.531.969-.969.531.969.531.531.969.531-.969.969-.531-.969-.531zm7.631 0l-9.125 9.125 2.875 2.875 9.125-9.125zm0 1.438l1.438 1.439-2.781 2.779-1.438-1.438z"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/src/m_pinentry/icons/visibility.svg b/src/m_pinentry/icons/visibility.svg
new file mode 100644
index 0000000..df91c9d
--- /dev/null
+++ b/src/m_pinentry/icons/visibility.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <g
+ transform="translate(-421.71429,-531.79074)">
+ <g
+ transform="matrix(0.75,0,0,0.74999813,421.46429,-241.22897)">
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 8 3 A 8.9999925 9.0000023 0 0 0 0.12304688 7.6679688 C 0.2519919 8.0317178 0.48048563 8.3445725 0.77929688 8.5761719 A 7.9999935 8.0000021 0 0 1 8 4 A 3.9999996 4.0000004 0 0 0 4 8 A 3.9999996 4.0000004 0 0 0 8 12 A 3.9999996 4.0000004 0 0 0 12 8 A 3.9999996 4.0000004 0 0 0 8.3496094 4.0175781 A 7.9999935 8.0000021 0 0 1 15.220703 8.5761719 C 15.522218 8.3424725 15.752612 8.0260772 15.880859 7.6582031 A 8.9999925 9.0000023 0 0 0 8 3 z M 8 5 A 2.9999996 3.0000002 0 0 1 11 8 A 2.9999996 3.0000002 0 0 1 8 11 A 2.9999996 3.0000002 0 0 1 5 8 A 2.9999996 3.0000002 0 0 1 8 5 z M 8 6 A 1.9999999 2.0000003 0 0 0 6 8 A 1.9999999 2.0000003 0 0 0 8 10 A 1.9999999 2.0000003 0 0 0 10 8 A 1.9999999 2.0000003 0 0 0 9.9101562 7.4121094 A 0.9999999 1 0 0 1 9 8 A 0.9999999 1 0 0 1 8 7 A 0.9999999 1 0 0 1 8.5898438 6.0898438 A 1.9999999 2.0000003 0 0 0 8 6 z "
+ transform="matrix(1.3333333,0,0,1.3333367,0.33333333,1030.6955)"
+ class="ColorScheme-Text"
+ id="rect4170" />
+ </g>
+ </g>
+</svg>
diff --git a/src/m_pinentry/pinentry.cpp b/src/m_pinentry/pinentry.cpp
new file mode 100644
index 0000000..981aee3
--- /dev/null
+++ b/src/m_pinentry/pinentry.cpp
@@ -0,0 +1,304 @@
+/* pinentry.c - The PIN entry support library
+ * Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015, 2016, 2021 g10 Code GmbH
+ *
+ * This file is part of PINENTRY.
+ *
+ * PINENTRY 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * PINENTRY 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "GFModuleCommonUtils.hpp"
+#include "GFSDKBasic.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef WINDOWS
+#include <errno.h>
+#endif
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#ifndef WINDOWS
+#include <sys/utsname.h>
+#endif
+#ifndef WINDOWS
+#include <locale.h>
+#endif
+#include <limits.h>
+#ifdef WINDOWS
+#include <windows.h>
+#endif
+
+#include <assuan.h>
+#include <qhash.h>
+
+#include "pinentry.h"
+
+#ifdef WINDOWS
+#define getpid() GetCurrentProcessId()
+#endif
+
+/* Keep the name of our program here. */
+static char this_pgmname[50];
+
+struct pinentry pinentry;
+
+static const char *flavor_flag;
+
+/* Return a malloced copy of the commandline for PID. If this is not
+ * possible NULL is returned. */
+#ifndef WINDOWS
+static char *get_cmdline(unsigned long pid) {
+ char buffer[200];
+ FILE *fp;
+ size_t i, n;
+
+ snprintf(buffer, sizeof buffer, "/proc/%lu/cmdline", pid);
+
+ fp = fopen(buffer, "rb");
+ if (!fp) return NULL;
+ n = fread(buffer, 1, sizeof buffer - 1, fp);
+ if (n < sizeof buffer - 1 && ferror(fp)) {
+ /* Some error occurred. */
+ fclose(fp);
+ return NULL;
+ }
+ fclose(fp);
+ if (n == 0) return NULL;
+ /* Arguments are delimited by Nuls. We should do proper quoting but
+ * that can be a bit complicated, thus we simply replace the Nuls by
+ * spaces. */
+ for (i = 0; i < n; i++)
+ if (!buffer[i] && i < n - 1) buffer[i] = ' ';
+ buffer[i] = 0; /* Make sure the last byte is the string terminator. */
+
+ return strdup(buffer);
+}
+#endif /*!WINDOWS*/
+
+/* Atomically ask the kernel for information about process PID.
+ * Return a malloc'ed copy of the process name as long as the process
+ * uid matches UID. If it cannot determine that the process has uid
+ * UID, it returns NULL.
+ *
+ * This is not as informative as get_cmdline, but it verifies that the
+ * process does belong to the user in question.
+ */
+#ifndef WINDOWS
+static char *get_pid_name_for_uid(unsigned long pid, int uid) {
+ char buffer[400];
+ FILE *fp;
+ size_t end, n;
+ char *uidstr;
+
+ snprintf(buffer, sizeof buffer, "/proc/%lu/status", pid);
+
+ fp = fopen(buffer, "rb");
+ if (!fp) return NULL;
+ n = fread(buffer, 1, sizeof buffer - 1, fp);
+ if (n < sizeof buffer - 1 && ferror(fp)) {
+ /* Some error occurred. */
+ fclose(fp);
+ return NULL;
+ }
+ fclose(fp);
+ if (n == 0) return NULL;
+ buffer[n] = 0;
+ /* Fixme: Is it specified that "Name" is always the first line? For
+ * robustness I would prefer to have a real parser here. -wk */
+ if (strncmp(buffer, "Name:\t", 6)) return NULL;
+ end = strcspn(buffer + 6, "\n") + 6;
+ buffer[end] = 0;
+
+ /* check that uid matches what we expect */
+ uidstr = strstr(buffer + end + 1, "\nUid:\t");
+ if (!uidstr) return NULL;
+ if (atoi(uidstr + 6) != uid) return NULL;
+
+ return strdup(buffer + 6);
+}
+#endif /*!WINDOWS*/
+
+const char *pinentry_get_pgmname(void) { return this_pgmname; }
+
+/* Return a malloced string with the title. The caller mus free the
+ * string. If no title is available or the title string has an error
+ * NULL is returned. */
+char *pinentry_get_title(pinentry_t pe) {
+ char *title;
+
+ if (pe->title) title = strdup(pe->title);
+#ifndef WINDOWS
+ else if (pe->owner_pid) {
+ char buf[200];
+ struct utsname utsbuf;
+ char *pidname = NULL;
+ char *cmdline = NULL;
+
+ if (pe->owner_host && !uname(&utsbuf) &&
+ !strcmp(utsbuf.nodename, pe->owner_host)) {
+ pidname = get_pid_name_for_uid(pe->owner_pid, pe->owner_uid);
+ if (pidname) cmdline = get_cmdline(pe->owner_pid);
+ }
+
+ if (pe->owner_host && (cmdline || pidname))
+ snprintf(buf, sizeof buf, "[%lu]@%s (%s)", pe->owner_pid, pe->owner_host,
+ cmdline ? cmdline : pidname);
+ else if (pe->owner_host)
+ snprintf(buf, sizeof buf, "[%lu]@%s", pe->owner_pid, pe->owner_host);
+ else
+ snprintf(buf, sizeof buf, "[%lu] <unknown host>", pe->owner_pid);
+ free(pidname);
+ free(cmdline);
+ title = strdup(buf);
+ }
+#endif /*!WINDOWS*/
+ else
+ title = strdup(this_pgmname);
+
+ return title;
+}
+
+/* Run a quality inquiry for PASSPHRASE of LENGTH. (We need LENGTH
+ because not all backends might be able to return a proper
+ C-string.). Returns: A value between -100 and 100 to give an
+ estimate of the passphrase's quality. Negative values are use if
+ the caller won't even accept that passphrase. Note that we expect
+ just one data line which should not be escaped in any represent a
+ numeric signed decimal value. Extra data is currently ignored but
+ should not be send at all. */
+int pinentry_inq_quality(const QString &passphrase) {
+ int score = 0;
+
+ score += std::min(40, static_cast<int>(passphrase.length()) * 2);
+
+ bool has_upper = false;
+ bool has_lower = false;
+ bool has_digit = false;
+ bool has_special = false;
+ for (const auto ch : passphrase) {
+ if (ch.isUpper()) has_upper = true;
+ if (ch.isLower()) has_lower = true;
+ if (ch.isDigit()) has_digit = true;
+ if (!ch.isLetterOrNumber()) has_special = true;
+ }
+
+ int const variety_count =
+ static_cast<int>(has_upper) + static_cast<int>(has_lower) +
+ static_cast<int>(has_digit) + static_cast<int>(has_special);
+ score += variety_count * 10;
+
+ for (auto i = 0; i < passphrase.length() - 1; ++i) {
+ if (passphrase[i] == passphrase[i + 1]) {
+ score -= 5;
+ }
+ }
+
+ QHash<QChar, int> char_count;
+ for (const auto ch : passphrase) {
+ char_count[ch]++;
+ }
+ for (auto &p : char_count) {
+ if (p > 1) {
+ score -= (p - 1) * 3;
+ }
+ }
+
+ QString const lower_password = passphrase.toLower();
+ if (lower_password.contains("password") ||
+ lower_password.contains("123456")) {
+ score -= 30;
+ }
+
+ return std::max(-100, std::min(100, score));
+}
+
+/* Run a genpin inquiry */
+char *pinentry_inq_genpin(pinentry_t pin) {
+ assuan_context_t ctx = (assuan_context_t)pin->ctx_assuan;
+ const char prefix[] = "INQUIRE GENPIN";
+ char *line;
+ size_t linelen;
+ int gotvalue = 0;
+ char *value = NULL;
+ int rc;
+
+ if (!ctx) return 0; /* Can't run the callback. */
+
+ rc = assuan_write_line(ctx, prefix);
+ if (rc) {
+ fprintf(stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc);
+ return 0;
+ }
+
+ for (;;) {
+ do {
+ rc = assuan_read_line(ctx, &line, &linelen);
+ if (rc) {
+ fprintf(stderr, "ASSUAN READ LINE failed: rc=%d\n", rc);
+ free(value);
+ return 0;
+ }
+ } while (*line == '#' || !linelen);
+ if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' &&
+ (!line[3] || line[3] == ' '))
+ break; /* END command received*/
+ if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' &&
+ (!line[3] || line[3] == ' '))
+ break; /* CAN command received*/
+ if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' &&
+ (!line[3] || line[3] == ' '))
+ break; /* ERR command received*/
+ if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) continue;
+ gotvalue = 1;
+ value = strdup(line + 2);
+ }
+
+ return value;
+}
+
+/* Try to make room for at least LEN bytes in the pinentry. Returns
+ new buffer on success and 0 on failure or when the old buffer is
+ sufficient. */
+char *pinentry_setbufferlen(pinentry_t pin, int len) {
+ char *newp;
+
+ if (pin->pin_len)
+ assert(pin->pin);
+ else
+ assert(!pin->pin);
+
+ if (len < 2048) len = 2048;
+
+ if (len <= pin->pin_len) return pin->pin;
+
+ newp = SecureReallocAsType<char>(pin->pin, len);
+ if (newp) {
+ pin->pin = newp;
+ pin->pin_len = len;
+ } else {
+ GFFreeMemory(pin->pin);
+ pin->pin = 0;
+ pin->pin_len = 0;
+ }
+ return newp;
+}
+
+/* Set the optional flag used with getinfo. */
+void pinentry_set_flavor_flag(const char *string) { flavor_flag = string; }
diff --git a/src/m_pinentry/pinentry.h b/src/m_pinentry/pinentry.h
new file mode 100644
index 0000000..143a885
--- /dev/null
+++ b/src/m_pinentry/pinentry.h
@@ -0,0 +1,339 @@
+/* pinentry.h - The interface for the PIN entry support library.
+ * Copyright (C) 2002, 2003, 2010, 2015, 2021 g10 Code GmbH
+ *
+ * This file is part of PINENTRY.
+ *
+ * PINENTRY 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * PINENTRY 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef PINENTRY_H
+#define PINENTRY_H
+
+#include <cstdint>
+
+#ifdef __cplusplus
+extern "C" {
+#if 0
+}
+#endif
+#endif
+
+typedef enum {
+ PINENTRY_COLOR_NONE,
+ PINENTRY_COLOR_DEFAULT,
+ PINENTRY_COLOR_BLACK,
+ PINENTRY_COLOR_RED,
+ PINENTRY_COLOR_GREEN,
+ PINENTRY_COLOR_YELLOW,
+ PINENTRY_COLOR_BLUE,
+ PINENTRY_COLOR_MAGENTA,
+ PINENTRY_COLOR_CYAN,
+ PINENTRY_COLOR_WHITE
+} pinentry_color_t;
+
+struct pinentry {
+ /* The window title, or NULL. (Assuan: "SETTITLE TITLE".) */
+ char *title;
+ /* The description to display, or NULL. (Assuan: "SETDESC
+ DESC".) */
+ char *description;
+ /* The error message to display, or NULL. (Assuan: "SETERROR
+ MESSAGE".) */
+ char *error;
+ /* The prompt to display, or NULL. (Assuan: "SETPROMPT
+ prompt".) */
+ char *prompt;
+ /* The OK button text to display, or NULL. (Assuan: "SETOK
+ OK".) */
+ char *ok;
+ /* The Not-OK button text to display, or NULL. This is the text for
+ the alternative option shown by the third button. (Assuan:
+ "SETNOTOK NOTOK".) */
+ char *notok;
+ /* The Cancel button text to display, or NULL. (Assuan: "SETCANCEL
+ CANCEL".) */
+ char *cancel;
+
+ /* The buffer to store the secret into. */
+ char *pin;
+ /* The length of the buffer. */
+ int pin_len;
+ /* Whether the pin was read from an external cache (1) or entered by
+ the user (0). */
+ int pin_from_cache;
+
+ /* The name of the X display to use if X is available and supported.
+ (Assuan: "OPTION display DISPLAY".) */
+ char *display;
+ /* The name of the terminal node to open if X not available or
+ supported. (Assuan: "OPTION ttyname TTYNAME".) */
+ char *ttyname;
+ /* The type of the terminal. (Assuan: "OPTION ttytype TTYTYPE".) */
+ char *ttytype_l;
+ /* Set the alert mode (none, beep or flash). */
+ char *ttyalert;
+ /* The LC_CTYPE value for the terminal. (Assuan: "OPTION lc-ctype
+ LC_CTYPE".) */
+ char *lc_ctype;
+ /* The LC_MESSAGES value for the terminal. (Assuan: "OPTION
+ lc-messages LC_MESSAGES".) */
+ char *lc_messages;
+
+ /* True if debug mode is requested. */
+ int debug;
+
+ /* The number of seconds before giving up while waiting for user input. */
+ int timeout;
+
+ /* True if caller should grab the keyboard. (Assuan: "OPTION grab"
+ or "OPTION no-grab".) */
+ int grab;
+
+ /* The PID of the owner or 0 if not known. The owner is the process
+ * which actually triggered the the pinentry. For example gpg. */
+ unsigned long owner_pid;
+
+ /* The numeric uid (user ID) of the owner process or -1 if not
+ * known. */
+ int owner_uid;
+
+ /* The malloced hostname of the owner or NULL. */
+ char *owner_host;
+
+ /* The window ID of the parent window over which the pinentry window
+ should be displayed. (Assuan: "OPTION parent-wid WID".) */
+ int parent_wid;
+
+ /* The name of an optional file which will be touched after a curses
+ entry has been displayed. (Assuan: "OPTION touch-file
+ FILENAME".) */
+ char *touch_file;
+
+ /* The frontend should set this to -1 if the user canceled the
+ request, and to the length of the PIN stored in pin
+ otherwise. */
+ int result;
+
+ /* The frontend should set this if the NOTOK button was pressed. */
+ int canceled;
+
+ /* The frontend should set this to true if an error with the local
+ conversion occurred. */
+ int locale_err;
+
+ /* The frontend should set this to a gpg-error so that commands are
+ able to return specific error codes. This is an ugly hack due to
+ the fact that pinentry_cmd_handler_t returns the length of the
+ passphrase or a negative error code. */
+ int specific_err;
+
+ /* The frontend may store a string with the error location here. */
+ const char *specific_err_loc;
+
+ /* The frontend may store a malloced string here to emit an ERROR
+ * status code with this extra info along with SPECIFIC_ERR. */
+ char *specific_err_info;
+
+ /* The frontend should set this to true if the window close button
+ has been used. This flag is used in addition to a regular return
+ value. */
+ int close_button;
+
+ /* The caller should set this to true if only one button is
+ required. This is useful for notification dialogs where only a
+ dismiss button is required. */
+ int one_button;
+
+ /* Whether this is a CONFIRM pinentry. */
+ int confirm;
+
+ /* If true a second prompt for the passphrase is shown and the user
+ is expected to enter the same passphrase again. Pinentry checks
+ that both match. (Assuan: "SETREPEAT".) */
+ char *repeat_passphrase;
+
+ /* The string to show if a repeated passphrase does not match.
+ (Assuan: "SETREPEATERROR ERROR".) */
+ char *repeat_error_string;
+
+ /* The string to show if a repeated passphrase does match.
+ (Assuan: "SETREPEATOK STRING".) */
+ char *repeat_ok_string;
+
+ /* Set to true if the passphrase has been entered a second time and
+ matches the first passphrase. */
+ int repeat_okay;
+
+ /* If this is not NULL, a passphrase quality indicator is shown.
+ There will also be an inquiry back to the caller to get an
+ indication of the quality for the passphrase entered so far. The
+ string is used as a label for the quality bar. (Assuan:
+ "SETQUALITYBAR LABEL".) */
+ char *quality_bar;
+
+ /* The tooltip to be shown for the qualitybar. Malloced or NULL.
+ (Assuan: "SETQUALITYBAR_TT TOOLTIP".) */
+ char *quality_bar_tt;
+
+ /* If this is not NULL, a generate action should be shown.
+ There will be an inquiry back to the caller to get such a
+ PIN. generate action. Malloced or NULL.
+ (Assuan: "SETGENPIN LABEL" .) */
+ char *genpin_label;
+
+ /* The tooltip to be shown for the generate action. Malloced or NULL.
+ (Assuan: "SETGENPIN_TT TOOLTIP".) */
+ char *genpin_tt;
+
+ /* Specifies whether passphrase formatting should be enabled.
+ (Assuan: "OPTION formatted-passphrase") */
+ int formatted_passphrase;
+
+ /* A hint to be shown near the passphrase input field if passphrase
+ formatting is enabled. Malloced or NULL.
+ (Assuan: "OPTION formatted-passphrase-hint=HINT".) */
+ char *formatted_passphrase_hint;
+
+ /* For the curses pinentry, the color of error messages. */
+ pinentry_color_t color_fg;
+ int color_fg_bright;
+ pinentry_color_t color_bg;
+ pinentry_color_t color_so;
+ int color_so_bright;
+ pinentry_color_t color_ok;
+ int color_ok_bright;
+ pinentry_color_t color_qualitybar;
+ int color_qualitybar_bright;
+
+ /* Malloced and i18ned default strings or NULL. These strings may
+ include an underscore character to indicate an accelerator key.
+ A double underscore represents a plain one. */
+ /* (Assuan: "OPTION default-ok OK"). */
+ char *default_ok;
+ /* (Assuan: "OPTION default-cancel CANCEL"). */
+ char *default_cancel;
+ /* (Assuan: "OPTION default-prompt PROMPT"). */
+ char *default_prompt;
+ /* (Assuan: "OPTION default-pwmngr
+ SAVE_PASSWORD_WITH_PASSWORD_MANAGER?"). */
+ char *default_pwmngr;
+ /* (Assuan: "OPTION default-cf-visi
+ Do you really want to make your passphrase visible?"). */
+ char *default_cf_visi;
+ /* (Assuan: "OPTION default-tt-visi
+ Make passphrase visible?"). */
+ char *default_tt_visi;
+ /* (Assuan: "OPTION default-tt-hide
+ Hide passphrase"). */
+ char *default_tt_hide;
+ /* (Assuan: "OPTION default-capshint
+ Caps Lock is on"). */
+ char *default_capshint;
+
+ /* Whether we are allowed to read the password from an external
+ cache. (Assuan: "OPTION allow-external-password-cache") */
+ int allow_external_password_cache;
+
+ /* We only try the cache once. */
+ int tried_password_cache;
+
+ /* A stable identifier for the key. (Assuan: "SETKEYINFO
+ KEYINFO".) */
+ char *keyinfo;
+
+ /* Whether we may cache the password (according to the user). */
+ int may_cache_password;
+
+ /* NOTE: If you add any additional fields to this structure, be sure
+ to update the initializer in pinentry/pinentry.c!!! */
+
+ /* For the quality indicator and genpin we need to do an inquiry.
+ Thus we need to save the assuan ctx. */
+ void *ctx_assuan;
+
+ /* An UTF-8 string with an invisible character used to override the
+ default in some pinentries. Only the first character is
+ used. */
+ char *invisible_char;
+
+ /* Whether the passphrase constraints are enforced by gpg-agent.
+ (Assuan: "OPTION constraints-enforce") */
+ int constraints_enforce;
+
+ /* A short translated hint for the user with the constraints for new
+ passphrases to be displayed near the passphrase input field.
+ Malloced or NULL.
+ (Assuan: "OPTION constraints-hint-short=At least 8 characters".) */
+ char *constraints_hint_short;
+
+ /* A longer translated hint for the user with the constraints for new
+ passphrases to be displayed for example as tooltip. Malloced or NULL.
+ (Assuan: "OPTION constraints-hint-long=The passphrase must ...".) */
+ char *constraints_hint_long;
+
+ /* A short translated title for an error dialog informing the user about
+ unsatisfied passphrase constraints. Malloced or NULL.
+ (Assuan: "OPTION constraints-error-title=Passphrase Not Allowed".) */
+ char *constraints_error_title;
+};
+typedef struct pinentry *pinentry_t;
+
+/* The pinentry command handler type processes the pinentry request
+ PIN. If PIN->pin is zero, request a confirmation, otherwise a PIN
+ entry. On confirmation, the function should return TRUE if
+ confirmed, and FALSE otherwise. On PIN entry, the function should
+ return -1 if an error occurred or the user cancelled the operation
+ and 1 otherwise. */
+typedef int (*pinentry_cmd_handler_t)(pinentry_t pin);
+
+const char *pinentry_get_pgmname(void);
+
+char *pinentry_get_title(pinentry_t pe);
+
+/* Run a quality inquiry for PASSPHRASE of LENGTH. */
+int pinentry_inq_quality(const QString &passphrase);
+
+/* Run a genpin iquriry. Returns a malloced string or NULL */
+char *pinentry_inq_genpin(pinentry_t pin);
+
+/* Try to make room for at least LEN bytes for the pin in the pinentry
+ PIN. Returns new buffer on success and 0 on failure. */
+char *pinentry_setbufferlen(pinentry_t pin, int len);
+
+/* Return true if either DISPLAY is set or ARGV contains the string
+ "--display". */
+int pinentry_have_display(int argc, char **argv);
+
+/* Parse the command line options. May exit the program if only help
+ or version output is requested. */
+void pinentry_parse_opts(int argc, char *argv[]);
+
+/* Set the optional flag used with getinfo. */
+void pinentry_set_flavor_flag(const char *string);
+
+#ifdef WINDOWS
+/* Windows declares sleep as obsolete, but provides a definition for
+ _sleep but non for the still existing sleep. */
+#define sleep(a) _sleep((a))
+#endif /*WINDOWS*/
+
+#if 0
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PINENTRY_H */
diff --git a/src/m_pinentry/pinentry.qrc b/src/m_pinentry/pinentry.qrc
new file mode 100644
index 0000000..3ecaed1
--- /dev/null
+++ b/src/m_pinentry/pinentry.qrc
@@ -0,0 +1,10 @@
+<!DOCTYPE RCC>
+<RCC>
+ <qresource prefix="/icons">
+ <file alias="data-error.svg">icons/data-error.svg</file>
+ <file alias="document-encrypt.png">icons/document-encrypt.png</file>
+ <file alias="hint.svg">icons/hint.svg</file>
+ <file alias="password-generate.svg">icons/password-generate.svg</file>
+ <file alias="visibility.svg">icons/visibility.svg</file>
+ </qresource>
+</RCC> \ No newline at end of file
diff --git a/src/m_pinentry/pinentry_debug.cpp b/src/m_pinentry/pinentry_debug.cpp
new file mode 100644
index 0000000..9afbcdb
--- /dev/null
+++ b/src/m_pinentry/pinentry_debug.cpp
@@ -0,0 +1,31 @@
+/* pinentry_debug.h - Logging category for pinentry
+ * Copyright (C) 2021 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "pinentry_debug.h"
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
+Q_LOGGING_CATEGORY(PINENTRY_LOG, "gpg.pinentry", QtWarningMsg)
+#else
+Q_LOGGING_CATEGORY(PINENTRY_LOG, "gpg.pinentry")
+#endif
diff --git a/src/m_pinentry/pinentry_debug.h b/src/m_pinentry/pinentry_debug.h
new file mode 100644
index 0000000..fc8c808
--- /dev/null
+++ b/src/m_pinentry/pinentry_debug.h
@@ -0,0 +1,28 @@
+/* pinentry_debug.h - Logging category for pinentry
+ * Copyright (C) 2021 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef __PINENTRY_QT_DEBUG_H__
+#define __PINENTRY_QT_DEBUG_H__
+
+#include <QLoggingCategory>
+
+Q_DECLARE_LOGGING_CATEGORY(PINENTRY_LOG)
+
+#endif // __PINENTRY_QT_DEBUG_H__
diff --git a/src/m_pinentry/pinentryconfirm.cpp b/src/m_pinentry/pinentryconfirm.cpp
new file mode 100644
index 0000000..31d55b5
--- /dev/null
+++ b/src/m_pinentry/pinentryconfirm.cpp
@@ -0,0 +1,123 @@
+/* pinentryconfirm.cpp - A QMessageBox with a timeout
+ *
+ * Copyright (C) 2011 Ben Kibbey <[email protected]>
+ * Copyright (C) 2022 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "pinentryconfirm.h"
+
+#include <QAbstractButton>
+#include <QApplication>
+#include <QFontMetrics>
+#include <QGridLayout>
+#include <QLabel>
+#include <QSpacerItem>
+
+#include "accessibility.h"
+#include "pinentrydialog.h"
+
+namespace {
+QLabel *messageBoxLabel(QMessageBox *messageBox) {
+ return messageBox->findChild<QLabel *>(QStringLiteral("qt_msgbox_label"));
+}
+} // namespace
+
+PinentryConfirm::PinentryConfirm(Icon icon, const QString &title,
+ const QString &text, StandardButtons buttons,
+ QWidget *parent, Qt::WindowFlags flags)
+ : QMessageBox{icon, title, text, buttons, parent, flags} {
+ _timer.callOnTimeout(this, &PinentryConfirm::slotTimeout);
+
+#ifndef QT_NO_ACCESSIBILITY
+ QAccessible::installActivationObserver(this);
+ accessibilityActiveChanged(QAccessible::isActive());
+#endif
+
+#if QT_VERSION >= 0x050000
+ /* This is in line with PinentryDialog ctor to have a maximizing
+ * animation when opening. */
+ if (qApp->platformName() != QLatin1String("wayland")) {
+ setWindowState(Qt::WindowMinimized);
+ QTimer::singleShot(0, this, [this]() { raiseWindow(this); });
+ }
+#else
+ activateWindow();
+ raise();
+#endif
+}
+
+PinentryConfirm::~PinentryConfirm() {
+#ifndef QT_NO_ACCESSIBILITY
+ QAccessible::removeActivationObserver(this);
+#endif
+}
+
+void PinentryConfirm::setTimeout(std::chrono::seconds timeout) {
+ _timer.setInterval(timeout);
+}
+
+std::chrono::seconds PinentryConfirm::timeout() const {
+ return std::chrono::duration_cast<std::chrono::seconds>(
+ _timer.intervalAsDuration());
+}
+
+bool PinentryConfirm::timedOut() const { return _timed_out; }
+
+void PinentryConfirm::showEvent(QShowEvent *event) {
+ static bool resized;
+ if (!resized) {
+ QGridLayout *lay = dynamic_cast<QGridLayout *>(layout());
+ if (lay) {
+ QSize textSize = fontMetrics().size(Qt::TextExpandTabs, text(),
+ fontMetrics().maxWidth());
+ QSpacerItem *horizontalSpacer =
+ new QSpacerItem(textSize.width() + iconPixmap().width(), 0,
+ QSizePolicy::Minimum, QSizePolicy::Expanding);
+ lay->addItem(horizontalSpacer, lay->rowCount(), 1, 1,
+ lay->columnCount() - 1);
+ }
+ resized = true;
+ }
+
+ QMessageBox::showEvent(event);
+
+ if (timeout() > std::chrono::milliseconds::zero()) {
+ _timer.setSingleShot(true);
+ _timer.start();
+ }
+}
+
+void PinentryConfirm::slotTimeout() {
+ QAbstractButton *b = button(QMessageBox::Cancel);
+ _timed_out = true;
+
+ if (b) {
+ b->animateClick();
+ }
+}
+
+#ifndef QT_NO_ACCESSIBILITY
+void PinentryConfirm::accessibilityActiveChanged(bool active) {
+ // Allow text label to get focus if accessibility is active
+ const auto focusPolicy = active ? Qt::StrongFocus : Qt::ClickFocus;
+ if (auto label = messageBoxLabel(this)) {
+ label->setFocusPolicy(focusPolicy);
+ }
+}
+#endif
diff --git a/src/m_pinentry/pinentryconfirm.h b/src/m_pinentry/pinentryconfirm.h
new file mode 100644
index 0000000..7be7c26
--- /dev/null
+++ b/src/m_pinentry/pinentryconfirm.h
@@ -0,0 +1,63 @@
+/* pinentryconfirm.h - A QMessageBox with a timeout
+ *
+ * Copyright (C) 2011 Ben Kibbey <[email protected]>
+ * Copyright (C) 2022 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef PINENTRYCONFIRM_H
+#define PINENTRYCONFIRM_H
+
+#include <QAccessible>
+#include <QMessageBox>
+#include <QTimer>
+
+class PinentryConfirm : public QMessageBox
+#ifndef QT_NO_ACCESSIBILITY
+ , public QAccessible::ActivationObserver
+#endif
+{
+ Q_OBJECT
+public:
+ PinentryConfirm(Icon icon, const QString &title, const QString &text,
+ StandardButtons buttons = NoButton, QWidget *parent = nullptr,
+ Qt::WindowFlags flags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint);
+ ~PinentryConfirm() override;
+
+ void setTimeout(std::chrono::seconds timeout);
+ std::chrono::seconds timeout() const;
+
+ bool timedOut() const;
+
+protected:
+ void showEvent(QShowEvent *event) override;
+
+private Q_SLOTS:
+ void slotTimeout();
+
+private:
+#ifndef QT_NO_ACCESSIBILITY
+ void accessibilityActiveChanged(bool active) override;
+#endif
+
+private:
+ QTimer _timer;
+ bool _timed_out = false;
+};
+
+#endif
diff --git a/src/m_pinentry/pinentrydialog.cpp b/src/m_pinentry/pinentrydialog.cpp
new file mode 100644
index 0000000..dcfdd50
--- /dev/null
+++ b/src/m_pinentry/pinentrydialog.cpp
@@ -0,0 +1,635 @@
+/* pinentrydialog.cpp - A (not yet) secure Qt 4 dialog for PIN entry.
+ * Copyright (C) 2002, 2008 Klarälvdalens Datakonsult AB (KDAB)
+ * Copyright 2007 Ingo Klöcker
+ * Copyright 2016 Intevation GmbH
+ * Copyright (C) 2021, 2022 g10 Code GmbH
+ *
+ * Written by Steffen Hansen <[email protected]>.
+ * Modified by Andre Heinecke <[email protected]>
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "pinentrydialog.h"
+
+#include <qnamespace.h>
+
+#include <QAccessible>
+#include <QAction>
+#include <QApplication>
+#include <QCheckBox>
+#include <QDebug>
+#include <QDialogButtonBox>
+#include <QFontMetrics>
+#include <QGridLayout>
+#include <QHBoxLayout>
+#include <QKeyEvent>
+#include <QLabel>
+#include <QLineEdit>
+#include <QMessageBox>
+#include <QPainter>
+#include <QPalette>
+#include <QProgressBar>
+#include <QPushButton>
+#include <QRegularExpression>
+#include <QStyle>
+#include <QVBoxLayout>
+
+#include "GFModuleCommonUtils.hpp"
+#include "accessibility.h"
+#include "capslock/capslock.h"
+#include "pinentry.h"
+#include "pinlineedit.h"
+
+void raiseWindow(QWidget *w) {
+ w->setWindowState((w->windowState() & ~Qt::WindowMinimized) |
+ Qt::WindowActive);
+ w->activateWindow();
+ w->raise();
+}
+
+auto applicationIconPixmap(const QIcon &overlayIcon) -> QPixmap {
+ QPixmap pm = qApp->windowIcon().pixmap(48, 48);
+
+ if (!overlayIcon.isNull()) {
+ QPainter painter(&pm);
+ const int emblem_size = 22;
+ painter.drawPixmap(pm.width() - emblem_size, 0,
+ overlayIcon.pixmap(emblem_size, emblem_size));
+ }
+
+ return pm;
+}
+
+void PinEntryDialog::slotTimeout() {
+ _timed_out = true;
+ reject();
+}
+
+PinEntryDialog::PinEntryDialog(QWidget *parent, const char *name, int timeout,
+ bool modal, bool enable_quality_bar,
+ const QString &repeatString,
+ const QString &visibilityTT,
+ const QString &hideTT)
+ : QDialog{parent},
+ _have_quality_bar{enable_quality_bar},
+ mVisibilityTT{visibilityTT},
+ mHideTT{hideTT} {
+ Q_UNUSED(name)
+
+ if (modal) {
+ setWindowModality(Qt::ApplicationModal);
+ setModal(true);
+ }
+
+ QPalette red_text_palette;
+ red_text_palette.setColor(QPalette::WindowText, Qt::red);
+
+ auto *const main_layout = new QVBoxLayout{this};
+
+ auto *const hbox = new QHBoxLayout;
+
+ _icon = new QLabel(this);
+ _icon->setPixmap(applicationIconPixmap());
+ hbox->addWidget(_icon, 0, Qt::AlignVCenter | Qt::AlignLeft);
+
+ auto *const grid = new QGridLayout;
+ int row = 1;
+
+ _error = new QLabel{this};
+ _error->setTextFormat(Qt::PlainText);
+ _error->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ _error->setPalette(red_text_palette);
+ _error->hide();
+ grid->addWidget(_error, row, 1, 1, 2);
+
+ row++;
+ _desc = new QLabel{this};
+ _desc->setTextFormat(Qt::PlainText);
+ _desc->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ _desc->hide();
+ grid->addWidget(_desc, row, 1, 1, 2);
+
+ row++;
+ mCapsLockHint = new QLabel{this};
+ mCapsLockHint->setTextFormat(Qt::PlainText);
+ mCapsLockHint->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ mCapsLockHint->setPalette(red_text_palette);
+ mCapsLockHint->setAlignment(Qt::AlignCenter);
+ mCapsLockHint->setVisible(false);
+ grid->addWidget(mCapsLockHint, row, 1, 1, 2);
+
+ row++;
+ {
+ _prompt = new QLabel(this);
+ _prompt->setTextFormat(Qt::PlainText);
+ _prompt->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ _prompt->hide();
+ grid->addWidget(_prompt, row, 1);
+
+ auto *const l = new QHBoxLayout;
+ _edit = new PinLineEdit(this);
+ _edit->setMaxLength(256);
+ _edit->setMinimumWidth(_edit->fontMetrics().averageCharWidth() * 20 + 48);
+ _edit->setEchoMode(QLineEdit::Password);
+ _prompt->setBuddy(_edit);
+ l->addWidget(_edit, 1);
+
+ if (!repeatString.isNull()) {
+ mGenerateButton = new QPushButton{this};
+ mGenerateButton->setIcon(
+ QIcon(QLatin1String(":/icons/password-generate.svg")));
+ mGenerateButton->setVisible(false);
+ l->addWidget(mGenerateButton);
+ }
+ grid->addLayout(l, row, 2);
+ }
+
+ /* Set up the show password action */
+ const QIcon visibility_icon = QIcon(QLatin1String(":/icons/visibility.svg"));
+ const QIcon hide_icon = QIcon(QLatin1String(":/icons/hint.svg"));
+#if QT_VERSION >= 0x050200
+ if (!visibility_icon.isNull() && !hide_icon.isNull()) {
+ mVisiActionEdit =
+ _edit->addAction(visibility_icon, QLineEdit::TrailingPosition);
+ mVisiActionEdit->setVisible(false);
+ mVisiActionEdit->setToolTip(mVisibilityTT);
+ } else
+#endif
+ {
+ if (!mVisibilityTT.isNull()) {
+ row++;
+ mVisiCB = new QCheckBox{mVisibilityTT, this};
+ grid->addWidget(mVisiCB, row, 1, 1, 2, Qt::AlignLeft);
+ }
+ }
+
+ row++;
+ mConstraintsHint = new QLabel{this};
+ mConstraintsHint->setTextFormat(Qt::PlainText);
+ mConstraintsHint->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ mConstraintsHint->setVisible(false);
+ grid->addWidget(mConstraintsHint, row, 2);
+
+ row++;
+ mFormattedPassphraseHintSpacer = new QLabel{this};
+ mFormattedPassphraseHintSpacer->setVisible(false);
+ mFormattedPassphraseHint = new QLabel{this};
+ mFormattedPassphraseHint->setTextFormat(Qt::PlainText);
+ mFormattedPassphraseHint->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ mFormattedPassphraseHint->setVisible(false);
+ grid->addWidget(mFormattedPassphraseHintSpacer, row, 1);
+ grid->addWidget(mFormattedPassphraseHint, row, 2);
+
+ if (!repeatString.isNull()) {
+ row++;
+ auto *repeat_label = new QLabel{this};
+ repeat_label->setTextFormat(Qt::PlainText);
+ repeat_label->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ repeat_label->setText(repeatString);
+ grid->addWidget(repeat_label, row, 1);
+
+ mRepeat = new PinLineEdit(this);
+ mRepeat->setMaxLength(256);
+ mRepeat->setEchoMode(QLineEdit::Password);
+ repeat_label->setBuddy(mRepeat);
+ grid->addWidget(mRepeat, row, 2);
+
+ row++;
+ mRepeatError = new QLabel{this};
+ mRepeatError->setTextFormat(Qt::PlainText);
+ mRepeatError->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ mRepeatError->setPalette(red_text_palette);
+ mRepeatError->hide();
+ grid->addWidget(mRepeatError, row, 2);
+ }
+
+ if (enable_quality_bar) {
+ row++;
+ _quality_bar_label = new QLabel(this);
+ _quality_bar_label->setTextFormat(Qt::PlainText);
+ _quality_bar_label->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ _quality_bar_label->setAlignment(Qt::AlignVCenter);
+ grid->addWidget(_quality_bar_label, row, 1);
+
+ _quality_bar = new QProgressBar(this);
+ _quality_bar->setAlignment(Qt::AlignCenter);
+ _quality_bar_label->setBuddy(_quality_bar);
+ grid->addWidget(_quality_bar, row, 2);
+ }
+
+ hbox->addLayout(grid, 1);
+ main_layout->addLayout(hbox);
+
+ auto *const buttons = new QDialogButtonBox(this);
+ buttons->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ _ok = buttons->button(QDialogButtonBox::Ok);
+ _cancel = buttons->button(QDialogButtonBox::Cancel);
+
+ if (style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons)) {
+ _ok->setIcon(style()->standardIcon(QStyle::SP_DialogOkButton));
+ _cancel->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton));
+ }
+
+ main_layout->addStretch(1);
+ main_layout->addWidget(buttons);
+ main_layout->setSizeConstraint(QLayout::SetFixedSize);
+
+ if (timeout > 0) {
+ _timer = new QTimer(this);
+ connect(_timer, &QTimer::timeout, this, &PinEntryDialog::slotTimeout);
+ _timer->start(timeout * 1000);
+ }
+
+ connect(buttons, &QDialogButtonBox::accepted, this,
+ &PinEntryDialog::onAccept);
+ connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
+ connect(_edit, &QLineEdit::textChanged, this, &PinEntryDialog::updateQuality);
+ connect(_edit, &QLineEdit::textChanged, this, &PinEntryDialog::textChanged);
+ connect(_edit, &PinLineEdit::backspacePressed, this,
+ &PinEntryDialog::onBackspace);
+ if (mGenerateButton != nullptr) {
+ connect(mGenerateButton, &QPushButton::clicked, this,
+ &PinEntryDialog::generatePin);
+ }
+ if (mVisiActionEdit != nullptr) {
+ connect(mVisiActionEdit, &QAction::triggered, this,
+ &PinEntryDialog::toggleVisibility);
+ }
+ if (mVisiCB != nullptr) {
+ connect(mVisiCB, &QCheckBox::toggled, this,
+ &PinEntryDialog::toggleVisibility);
+ }
+ if (mRepeat != nullptr) {
+ connect(mRepeat, &QLineEdit::textChanged, this,
+ &PinEntryDialog::textChanged);
+ }
+
+ auto *caps_lock_watcher = new CapsLockWatcher{this};
+ connect(caps_lock_watcher, &CapsLockWatcher::stateChanged, this,
+ [this](bool locked) { mCapsLockHint->setVisible(locked); });
+
+ connect(qApp, &QApplication::focusChanged, this,
+ &PinEntryDialog::focusChanged);
+ connect(qApp, &QApplication::applicationStateChanged, this,
+ &PinEntryDialog::checkCapsLock);
+ checkCapsLock();
+
+ setAttribute(Qt::WA_DeleteOnClose);
+ setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
+
+ /* This is mostly an issue on Windows where this results
+ in the pinentry popping up nicely with an animation and
+ comes to front. It is not ifdefed for Windows only since
+ window managers on Linux like KWin can also have this
+ result in an animation when the pinentry is shown and
+ not just popping it up.
+ */
+ if (qApp->platformName() != QLatin1String("wayland")) {
+ setWindowState(Qt::WindowMinimized);
+ QTimer::singleShot(0, this, [this]() { raiseWindow(this); });
+ } else {
+ raiseWindow(this);
+ }
+}
+
+void PinEntryDialog::keyPressEvent(QKeyEvent *e) {
+ const auto return_pressed =
+ (!e->modifiers() &&
+ (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return)) ||
+ (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter);
+ if (return_pressed && _edit->hasFocus() && (mRepeat != nullptr)) {
+ // if the user pressed Return in the first input field, then move the
+ // focus to the repeat input field and prevent further event processing
+ // by QDialog (which would trigger the default button)
+ mRepeat->setFocus();
+ e->ignore();
+ return;
+ }
+
+ QDialog::keyPressEvent(e);
+}
+
+void PinEntryDialog::keyReleaseEvent(QKeyEvent *event) {
+ QDialog::keyReleaseEvent(event);
+ checkCapsLock();
+}
+
+void PinEntryDialog::showEvent(QShowEvent *event) {
+ QDialog::showEvent(event);
+ _edit->setFocus();
+}
+
+void PinEntryDialog::setDescription(const QString &txt) {
+ _desc->setVisible(!txt.isEmpty());
+ _desc->setText(txt);
+ _icon->setPixmap(applicationIconPixmap());
+ setError(QString());
+}
+
+QString PinEntryDialog::description() const { return _desc->text(); }
+
+void PinEntryDialog::setError(const QString &txt) {
+ if (!txt.isNull()) {
+ _icon->setPixmap(
+ applicationIconPixmap(QIcon{QStringLiteral(":/icons/data-error.svg")}));
+ }
+ _error->setText(txt);
+ _error->setVisible(!txt.isEmpty());
+}
+
+QString PinEntryDialog::error() const { return _error->text(); }
+
+void PinEntryDialog::setPin(const QString &txt) { _edit->setPin(txt); }
+
+QString PinEntryDialog::pin() const { return _edit->pin(); }
+
+void PinEntryDialog::setPrompt(const QString &txt) {
+ _prompt->setText(txt);
+ _prompt->setVisible(!txt.isEmpty());
+ if (txt.contains("PIN")) _disable_echo_allowed = false;
+}
+
+QString PinEntryDialog::prompt() const { return _prompt->text(); }
+
+void PinEntryDialog::setOkText(const QString &txt) {
+ _ok->setText(txt);
+ _ok->setVisible(!txt.isEmpty());
+}
+
+void PinEntryDialog::setCancelText(const QString &txt) {
+ _cancel->setText(txt);
+ _cancel->setVisible(!txt.isEmpty());
+}
+
+void PinEntryDialog::setQualityBar(const QString &txt) {
+ if (_have_quality_bar) {
+ _quality_bar_label->setText(txt);
+ }
+}
+
+void PinEntryDialog::setQualityBarTT(const QString &txt) {
+ if (_have_quality_bar) {
+ _quality_bar->setToolTip(txt);
+ }
+}
+
+void PinEntryDialog::setGenpinLabel(const QString &txt) {
+ if (mGenerateButton == nullptr) {
+ return;
+ }
+ mGenerateButton->setVisible(!txt.isEmpty());
+ if (!txt.isEmpty()) {
+ Accessibility::setName(mGenerateButton, txt);
+ }
+}
+
+void PinEntryDialog::setGenpinTT(const QString &txt) {
+ if (mGenerateButton != nullptr) {
+ mGenerateButton->setToolTip(txt);
+ }
+}
+
+void PinEntryDialog::setCapsLockHint(const QString &txt) {
+ mCapsLockHint->setText(txt);
+}
+
+void PinEntryDialog::setFormattedPassphrase(
+ const PinEntryDialog::FormattedPassphraseOptions &options) {
+ mFormatPassphrase = options.formatPassphrase;
+ mFormattedPassphraseHint->setTextFormat(Qt::RichText);
+ mFormattedPassphraseHint->setText(QLatin1String("<html>") +
+ options.hint.toHtmlEscaped() +
+ QLatin1String("</html>"));
+ Accessibility::setName(mFormattedPassphraseHint, options.hint);
+ // toggleFormattedPassphrase();
+}
+
+void PinEntryDialog::setConstraintsOptions(const ConstraintsOptions &options) {
+ mEnforceConstraints = options.enforce;
+ mConstraintsHint->setText(options.shortHint);
+ if (!options.longHint.isEmpty()) {
+ mConstraintsHint->setToolTip(
+ QLatin1String("<html>") +
+ options.longHint.toHtmlEscaped().replace(QLatin1String("\n\n"),
+ QLatin1String("<br>")) +
+ QLatin1String("</html>"));
+ Accessibility::setDescription(mConstraintsHint, options.longHint);
+ }
+ mConstraintsErrorTitle = options.errorTitle;
+
+ mConstraintsHint->setVisible(mEnforceConstraints &&
+ !options.shortHint.isEmpty());
+}
+
+void PinEntryDialog::toggleFormattedPassphrase() {
+ const bool enable_formatting =
+ mFormatPassphrase && _edit->echoMode() == QLineEdit::Normal;
+ _edit->setFormattedPassphrase(enable_formatting);
+ if (mRepeat != nullptr) {
+ mRepeat->setFormattedPassphrase(enable_formatting);
+ const bool hint_about_to_be_hidden =
+ mFormattedPassphraseHint->isVisible() && !enable_formatting;
+ if (hint_about_to_be_hidden) {
+ // set hint spacer to current height of hint label before hiding the hint
+ mFormattedPassphraseHintSpacer->setMinimumHeight(
+ mFormattedPassphraseHint->height());
+ mFormattedPassphraseHintSpacer->setVisible(true);
+ } else if (enable_formatting) {
+ mFormattedPassphraseHintSpacer->setVisible(false);
+ }
+ mFormattedPassphraseHint->setVisible(enable_formatting);
+ }
+}
+
+void PinEntryDialog::onBackspace() {
+ cancelTimeout();
+
+ if (_disable_echo_allowed) {
+ _edit->setEchoMode(QLineEdit::NoEcho);
+ if (mRepeat != nullptr) {
+ mRepeat->setEchoMode(QLineEdit::NoEcho);
+ }
+ }
+}
+
+void PinEntryDialog::updateQuality(const QString &txt) {
+ int length;
+ int percent;
+ QPalette pal;
+
+ _disable_echo_allowed = false;
+
+ if (!_have_quality_bar) {
+ return;
+ }
+
+ length = txt.length();
+ percent = length != 0 ? pinentry_inq_quality(txt) : 0;
+ if (length == 0) {
+ _quality_bar->reset();
+ } else {
+ pal = _quality_bar->palette();
+ if (percent < 0) {
+ pal.setColor(QPalette::Highlight, QColor("red"));
+ percent = -percent;
+ } else {
+ pal.setColor(QPalette::Highlight, QColor("green"));
+ }
+ _quality_bar->setPalette(pal);
+ _quality_bar->setValue(percent);
+ }
+}
+
+void PinEntryDialog::setPinentryInfo(struct pinentry peinfo) {
+ _pinentry_info = SecureCreateQSharedObject<struct pinentry>(peinfo);
+}
+
+void PinEntryDialog::focusChanged(QWidget *old, QWidget *now) {
+ // Grab keyboard. It might be a little weird to do it here, but it works!
+ // Previously this code was in showEvent, but that did not work in Qt4.
+ if (!_pinentry_info || (_pinentry_info->grab != 0)) {
+ if (_grabbed && (old != nullptr) && (old == _edit || old == mRepeat)) {
+ old->releaseKeyboard();
+ _grabbed = false;
+ }
+ if (!_grabbed && (now != nullptr) && (now == _edit || now == mRepeat)) {
+ now->grabKeyboard();
+ _grabbed = true;
+ }
+ }
+}
+
+void PinEntryDialog::textChanged(const QString &text) {
+ Q_UNUSED(text);
+
+ cancelTimeout();
+
+ if ((mVisiActionEdit != nullptr) && sender() == _edit) {
+ mVisiActionEdit->setVisible(!_edit->pin().isEmpty());
+ }
+ if (mGenerateButton != nullptr) {
+ mGenerateButton->setVisible(_edit->pin().isEmpty()
+#ifndef QT_NO_ACCESSIBILITY
+ && !mGenerateButton->accessibleName().isEmpty()
+#endif
+ );
+ }
+}
+
+void PinEntryDialog::generatePin() {
+ // std::unique_ptr<char> pin{pinentry_inq_genpin(_pinentry_info.get())};
+ // if (pin) {
+ // if (_edit->echoMode() == QLineEdit::Password) {
+ // if (mVisiActionEdit != nullptr) {
+ // mVisiActionEdit->trigger();
+ // }
+ // if (mVisiCB != nullptr) {
+ // mVisiCB->setChecked(true);
+ // }
+ // }
+ // const auto pin_str = QString::fromUtf8(pin.get());
+ // _edit->setPin(pin_str);
+ // mRepeat->setPin(pin_str);
+ // // explicitly focus the first input field and select the generated
+ // password _edit->setFocus(); _edit->selectAll();
+ // }
+}
+
+void PinEntryDialog::toggleVisibility() {
+ if (sender() != mVisiCB) {
+ if (_edit->echoMode() == QLineEdit::Password) {
+ if (mVisiActionEdit != nullptr) {
+ mVisiActionEdit->setIcon(QIcon(QLatin1String(":/icons/hint.svg")));
+ mVisiActionEdit->setToolTip(mHideTT);
+ }
+ _edit->setEchoMode(QLineEdit::Normal);
+ if (mRepeat != nullptr) {
+ mRepeat->setEchoMode(QLineEdit::Normal);
+ }
+ } else {
+ if (mVisiActionEdit != nullptr) {
+ mVisiActionEdit->setIcon(
+ QIcon(QLatin1String(":/icons/visibility.svg")));
+ mVisiActionEdit->setToolTip(mVisibilityTT);
+ }
+ _edit->setEchoMode(QLineEdit::Password);
+ if (mRepeat != nullptr) {
+ mRepeat->setEchoMode(QLineEdit::Password);
+ }
+ }
+ } else {
+ if (mVisiCB->isChecked()) {
+ if (mRepeat != nullptr) {
+ mRepeat->setEchoMode(QLineEdit::Normal);
+ }
+ _edit->setEchoMode(QLineEdit::Normal);
+ } else {
+ if (mRepeat != nullptr) {
+ mRepeat->setEchoMode(QLineEdit::Password);
+ }
+ _edit->setEchoMode(QLineEdit::Password);
+ }
+ }
+ toggleFormattedPassphrase();
+}
+
+QString PinEntryDialog::repeatedPin() const {
+ if (mRepeat != nullptr) {
+ return mRepeat->pin();
+ }
+ return QString();
+}
+
+bool PinEntryDialog::timedOut() const { return _timed_out; }
+
+void PinEntryDialog::setRepeatErrorText(const QString &err) {
+ if (mRepeatError != nullptr) {
+ mRepeatError->setText(err);
+ }
+}
+
+void PinEntryDialog::cancelTimeout() {
+ if (_timer != nullptr) {
+ _timer->stop();
+ }
+}
+
+void PinEntryDialog::checkCapsLock() {
+ const auto state = capsLockState();
+ if (state != LockState::Unknown) {
+ mCapsLockHint->setVisible(state == LockState::On);
+ }
+}
+
+void PinEntryDialog::onAccept() {
+ cancelTimeout();
+
+ if ((mRepeat != nullptr) && mRepeat->pin() != _edit->pin()) {
+#ifndef QT_NO_ACCESSIBILITY
+ if (QAccessible::isActive()) {
+ QMessageBox::information(this, mRepeatError->text(),
+ mRepeatError->text());
+ } else
+#endif
+ {
+ mRepeatError->setVisible(true);
+ }
+ return;
+ }
+
+ accept();
+}
diff --git a/src/m_pinentry/pinentrydialog.h b/src/m_pinentry/pinentrydialog.h
new file mode 100644
index 0000000..c7d689e
--- /dev/null
+++ b/src/m_pinentry/pinentrydialog.h
@@ -0,0 +1,169 @@
+/* pinentrydialog.h - A (not yet) secure Qt 4 dialog for PIN entry.
+ * Copyright (C) 2002, 2008 Klarälvdalens Datakonsult AB (KDAB)
+ * Copyright 2007 Ingo Klöcker
+ * Copyright 2016 Intevation GmbH
+ * Copyright (C) 2021, 2022 g10 Code GmbH
+ *
+ * Written by Steffen Hansen <[email protected]>.
+ * Modified by Andre Heinecke <[email protected]>
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef __PINENTRYDIALOG_H__
+#define __PINENTRYDIALOG_H__
+
+#include <QAccessible>
+#include <QDialog>
+#include <QStyle>
+#include <QTimer>
+
+#include "pinentry.h"
+
+class QIcon;
+class QLabel;
+class QPushButton;
+class QLineEdit;
+class PinLineEdit;
+class QString;
+class QProgressBar;
+class QCheckBox;
+class QAction;
+
+QPixmap applicationIconPixmap(const QIcon &overlayIcon = {});
+
+void raiseWindow(QWidget *w);
+
+class PinEntryDialog : public QDialog {
+ Q_OBJECT
+
+ Q_PROPERTY(QString description READ description WRITE setDescription)
+ Q_PROPERTY(QString error READ error WRITE setError)
+ Q_PROPERTY(QString pin READ pin WRITE setPin)
+ Q_PROPERTY(QString prompt READ prompt WRITE setPrompt)
+ public:
+ struct FormattedPassphraseOptions {
+ bool formatPassphrase;
+ QString hint;
+ };
+ struct ConstraintsOptions {
+ bool enforce;
+ QString shortHint;
+ QString longHint;
+ QString errorTitle;
+ };
+
+ explicit PinEntryDialog(QWidget *parent = 0, const char *name = 0,
+ int timeout = 0, bool modal = false,
+ bool enable_quality_bar = false,
+ const QString &repeatString = QString(),
+ const QString &visibiltyTT = QString(),
+ const QString &hideTT = QString());
+
+ void setDescription(const QString &);
+ QString description() const;
+
+ void setError(const QString &);
+ QString error() const;
+
+ void setPin(const QString &);
+ QString pin() const;
+
+ QString repeatedPin() const;
+ void setRepeatErrorText(const QString &);
+
+ void setPrompt(const QString &);
+ QString prompt() const;
+
+ void setOkText(const QString &);
+ void setCancelText(const QString &);
+
+ void setQualityBar(const QString &);
+ void setQualityBarTT(const QString &);
+
+ void setGenpinLabel(const QString &);
+ void setGenpinTT(const QString &);
+
+ void setCapsLockHint(const QString &);
+
+ void setFormattedPassphrase(const FormattedPassphraseOptions &options);
+
+ void setConstraintsOptions(const ConstraintsOptions &options);
+
+ void setPinentryInfo(struct pinentry);
+
+ bool timedOut() const;
+
+ protected Q_SLOTS:
+ void updateQuality(const QString &);
+ void slotTimeout();
+ void textChanged(const QString &);
+ void focusChanged(QWidget *old, QWidget *now);
+ void toggleVisibility();
+ void onBackspace();
+ void generatePin();
+ void toggleFormattedPassphrase();
+
+ protected:
+ void keyPressEvent(QKeyEvent *event) override;
+ void keyReleaseEvent(QKeyEvent *event) override;
+ void showEvent(QShowEvent *event) override;
+
+ private Q_SLOTS:
+ void cancelTimeout();
+ void checkCapsLock();
+ void onAccept();
+
+ private:
+ enum PassphraseCheckResult {
+ PassphraseNotChecked = -1,
+ PassphraseNotOk = 0,
+ PassphraseOk
+ };
+
+ QLabel *_icon = nullptr;
+ QLabel *_desc = nullptr;
+ QLabel *_error = nullptr;
+ QLabel *_prompt = nullptr;
+ QLabel *_quality_bar_label = nullptr;
+ QProgressBar *_quality_bar = nullptr;
+ PinLineEdit *_edit = nullptr;
+ PinLineEdit *mRepeat = nullptr;
+ QLabel *mRepeatError = nullptr;
+ QPushButton *_ok = nullptr;
+ QPushButton *_cancel = nullptr;
+ bool _grabbed = false;
+ bool _have_quality_bar = false;
+ bool _timed_out = false;
+ bool _disable_echo_allowed = true;
+ bool mEnforceConstraints = false;
+ bool mFormatPassphrase = false;
+
+ QSharedPointer<struct pinentry> _pinentry_info = nullptr;
+ QTimer *_timer = nullptr;
+ QString mVisibilityTT;
+ QString mHideTT;
+ QAction *mVisiActionEdit = nullptr;
+ QPushButton *mGenerateButton = nullptr;
+ QCheckBox *mVisiCB = nullptr;
+ QLabel *mFormattedPassphraseHint = nullptr;
+ QLabel *mFormattedPassphraseHintSpacer = nullptr;
+ QLabel *mCapsLockHint = nullptr;
+ QLabel *mConstraintsHint = nullptr;
+ QString mConstraintsErrorTitle;
+};
+
+#endif // __PINENTRYDIALOG_H__
diff --git a/src/m_pinentry/pinlineedit.cpp b/src/m_pinentry/pinlineedit.cpp
new file mode 100644
index 0000000..9d172b5
--- /dev/null
+++ b/src/m_pinentry/pinlineedit.cpp
@@ -0,0 +1,204 @@
+/* pinlineedit.cpp - Modified QLineEdit widget.
+ * Copyright (C) 2018 Damien Goutte-Gattat
+ * Copyright (C) 2021 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "pinlineedit.h"
+
+#include <QClipboard>
+#include <QGuiApplication>
+#include <QKeyEvent>
+
+static const int FormattedPassphraseGroupSize = 5;
+static const QChar FormattedPassphraseSeparator = QChar::Nbsp;
+
+namespace {
+struct Selection {
+ bool empty() const { return start < 0 || start >= end; }
+ int length() const { return empty() ? 0 : end - start; }
+
+ int start;
+ int end;
+};
+} // namespace
+
+class PinLineEdit::Private {
+ PinLineEdit *const q;
+
+ public:
+ Private(PinLineEdit *q) : q{q} {}
+
+ QString formatted(QString text) const {
+ const int dashCount = text.size() / FormattedPassphraseGroupSize;
+ text.reserve(text.size() + dashCount);
+ for (int i = FormattedPassphraseGroupSize; i < text.size();
+ i += FormattedPassphraseGroupSize + 1) {
+ text.insert(i, FormattedPassphraseSeparator);
+ }
+ return text;
+ }
+
+ Selection formattedSelection(Selection selection) const {
+ if (selection.empty()) {
+ return selection;
+ }
+ return {selection.start + selection.start / FormattedPassphraseGroupSize,
+ selection.end + (selection.end - 1) / FormattedPassphraseGroupSize};
+ }
+
+ QString unformatted(QString text) const {
+ for (int i = FormattedPassphraseGroupSize; i < text.size();
+ i += FormattedPassphraseGroupSize) {
+ text.remove(i, 1);
+ }
+ return text;
+ }
+
+ Selection unformattedSelection(Selection selection) const {
+ if (selection.empty()) {
+ return selection;
+ }
+ return {
+ selection.start - selection.start / (FormattedPassphraseGroupSize + 1),
+ selection.end - selection.end / (FormattedPassphraseGroupSize + 1)};
+ }
+
+ void copyToClipboard() {
+ if (q->echoMode() != QLineEdit::Normal) {
+ return;
+ }
+
+ QString text = q->selectedText();
+ if (mFormattedPassphrase) {
+ text.remove(FormattedPassphraseSeparator);
+ }
+ if (!text.isEmpty()) {
+ QGuiApplication::clipboard()->setText(text);
+ }
+ }
+
+ int selectionEnd() {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
+ return q->selectionEnd();
+#else
+ return q->selectionStart() + q->selectedText().size();
+#endif
+ }
+
+ public:
+ bool mFormattedPassphrase = false;
+};
+
+PinLineEdit::PinLineEdit(QWidget *parent)
+ : QLineEdit(parent), d{new Private{this}} {
+ connect(this, SIGNAL(textEdited(QString)), this, SLOT(textEdited()));
+}
+
+PinLineEdit::~PinLineEdit() = default;
+
+void PinLineEdit::setFormattedPassphrase(bool on) {
+ if (on == d->mFormattedPassphrase) {
+ return;
+ }
+ d->mFormattedPassphrase = on;
+ Selection selection{selectionStart(), d->selectionEnd()};
+ if (d->mFormattedPassphrase) {
+ setText(d->formatted(text()));
+ selection = d->formattedSelection(selection);
+ } else {
+ setText(d->unformatted(text()));
+ selection = d->unformattedSelection(selection);
+ }
+ if (!selection.empty()) {
+ setSelection(selection.start, selection.length());
+ }
+}
+
+void PinLineEdit::copy() const { d->copyToClipboard(); }
+
+void PinLineEdit::cut() {
+ if (hasSelectedText()) {
+ copy();
+ del();
+ }
+}
+
+void PinLineEdit::setPin(const QString &pin) {
+ setText(d->mFormattedPassphrase ? d->formatted(pin) : pin);
+}
+
+QString PinLineEdit::pin() const {
+ if (d->mFormattedPassphrase) {
+ return d->unformatted(text());
+ } else {
+ return text();
+ }
+}
+
+void PinLineEdit::keyPressEvent(QKeyEvent *e) {
+ if (e == QKeySequence::Copy) {
+ copy();
+ return;
+ } else if (e == QKeySequence::Cut) {
+ if (!isReadOnly() && hasSelectedText()) {
+ copy();
+ del();
+ }
+ return;
+ } else if (e == QKeySequence::DeleteEndOfLine) {
+ if (!isReadOnly()) {
+ setSelection(cursorPosition(), text().size());
+ copy();
+ del();
+ }
+ return;
+ } else if (e == QKeySequence::DeleteCompleteLine) {
+ if (!isReadOnly()) {
+ setSelection(0, text().size());
+ copy();
+ del();
+ }
+ return;
+ }
+
+ QLineEdit::keyPressEvent(e);
+
+ if (e->key() == Qt::Key::Key_Backspace) {
+ emit backspacePressed();
+ }
+}
+
+void PinLineEdit::textEdited() {
+ if (!d->mFormattedPassphrase) {
+ return;
+ }
+ auto currentText = text();
+ // first calculate the cursor position in the reformatted text; the cursor
+ // is put left of the separators, so that backspace works as expected
+ auto cursorPos = cursorPosition();
+ cursorPos -= QStringView{currentText}.left(cursorPos).count(
+ FormattedPassphraseSeparator);
+ cursorPos += std::max(cursorPos - 1, 0) / FormattedPassphraseGroupSize;
+ // then reformat the text
+ currentText.remove(FormattedPassphraseSeparator);
+ currentText = d->formatted(currentText);
+ // finally, set reformatted text and updated cursor position
+ setText(currentText);
+ setCursorPosition(cursorPos);
+}
diff --git a/src/m_pinentry/pinlineedit.h b/src/m_pinentry/pinlineedit.h
new file mode 100644
index 0000000..72ac85a
--- /dev/null
+++ b/src/m_pinentry/pinlineedit.h
@@ -0,0 +1,60 @@
+/* pinlineedit.h - Modified QLineEdit widget.
+ * Copyright (C) 2018 Damien Goutte-Gattat
+ * Copyright (C) 2021 g10 Code GmbH
+ *
+ * Software engineering by Ingo Klöcker <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef _PINLINEEDIT_H_
+#define _PINLINEEDIT_H_
+
+#include <QLineEdit>
+
+class PinLineEdit : public QLineEdit {
+ Q_OBJECT
+
+ public:
+ explicit PinLineEdit(QWidget *parent = nullptr);
+ ~PinLineEdit() override;
+
+ void setPin(const QString &pin);
+ QString pin() const;
+
+ public Q_SLOTS:
+ void setFormattedPassphrase(bool on);
+ void copy() const;
+ void cut();
+
+ Q_SIGNALS:
+ void backspacePressed();
+
+ protected:
+ void keyPressEvent(QKeyEvent *) override;
+
+ private:
+ using QLineEdit::setText;
+ using QLineEdit::text;
+
+ private Q_SLOTS:
+ void textEdited();
+
+ private:
+ class Private;
+ std::unique_ptr<Private> d;
+};
+
+#endif // _PINLINEEDIT_H_
diff --git a/src/m_pinentry/qti18n.cpp b/src/m_pinentry/qti18n.cpp
new file mode 100644
index 0000000..198e6cc
--- /dev/null
+++ b/src/m_pinentry/qti18n.cpp
@@ -0,0 +1,93 @@
+/* qti18n.cpp - Load qt translations for pinentry.
+ * Copyright 2021 g10 Code GmbH
+ * SPDX-FileCopyrightText: 2015 Lukáš Tinkl <[email protected]>
+ * SPDX-FileCopyrightText: 2021 Ingo Klöcker <[email protected]>
+ *
+ * Copied from k18n under the terms of LGPLv2 or later.
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QLibraryInfo>
+#include <QLocale>
+#include <QTranslator>
+
+static bool loadCatalog(const QString &catalog, const QLocale &locale) {
+ auto translator = new QTranslator(QCoreApplication::instance());
+
+ if (!translator->load(locale, catalog, QString(),
+ QLatin1String(":/i18n_qt"))) {
+ qDebug() << "Loading the" << catalog << "catalog failed for locale"
+ << locale;
+ delete translator;
+ return false;
+ }
+ QCoreApplication::instance()->installTranslator(translator);
+ return true;
+}
+
+static bool loadCatalog(const QString &catalog, const QLocale &locale,
+ const QLocale &fallbackLocale) {
+ // try to load the catalog for locale
+ if (loadCatalog(catalog, locale)) {
+ return true;
+ }
+ // if this fails, then try the fallback locale (if it's different from locale)
+ if (fallbackLocale != locale) {
+ return loadCatalog(catalog, fallbackLocale);
+ }
+ return false;
+}
+
+// load global Qt translation, needed in KDE e.g. by lots of builtin dialogs
+// (QColorDialog, QFontDialog) that we use
+static void loadTranslation(const QString &localeName,
+ const QString &fallbackLocaleName) {
+ const QLocale locale{localeName};
+ const QLocale fallbackLocale{fallbackLocaleName};
+ // first, try to load the qt_ meta catalog
+ if (loadCatalog(QStringLiteral("qt_"), locale, fallbackLocale)) {
+ return;
+ }
+ // if loading the meta catalog failed, then try loading the four catalogs
+ // it depends on, i.e. qtbase, qtscript, qtmultimedia, qtxmlpatterns,
+ // separately
+ const auto catalogs = {
+ QStringLiteral("qtbase_"),
+ /* QStringLiteral("qtscript_"),
+ QStringLiteral("qtmultimedia_"),
+ QStringLiteral("qtxmlpatterns_"), */
+ };
+ for (const auto &catalog : catalogs) {
+ loadCatalog(catalog, locale, fallbackLocale);
+ }
+}
+
+static void load() {
+ // The way Qt translation system handles plural forms makes it necessary to
+ // have a translation file which contains only plural forms for `en`. That's
+ // why we load the `en` translation unconditionally, then load the
+ // translation for the current locale to overload it.
+ loadCatalog(QStringLiteral("qt_"), QLocale{QStringLiteral("en")});
+
+ const QLocale locale = QLocale::system();
+ if (locale.name() != QStringLiteral("en")) {
+ loadTranslation(locale.name(), locale.bcp47Name());
+ }
+}
+
+Q_COREAPP_STARTUP_FUNCTION(load)
diff --git a/src/m_pinentry/secmem++.h b/src/m_pinentry/secmem++.h
new file mode 100644
index 0000000..116da88
--- /dev/null
+++ b/src/m_pinentry/secmem++.h
@@ -0,0 +1,91 @@
+/* STL allocator for secmem
+ * Copyright (C) 2008 Marc Mutz <[email protected]>
+ *
+ * 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 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef __SECMEM_SECMEMPP_H__
+#define __SECMEM_SECMEMPP_H__
+
+#include "../secmem/secmem.h"
+#include <cstddef>
+
+namespace secmem {
+
+ template <typename T>
+ class alloc {
+ public:
+ // type definitions:
+ typedef size_t size_type;
+ typedef ptrdiff_t difference_type;
+ typedef T* pointer;
+ typedef const T* const_pointer;
+ typedef T& reference;
+ typedef const T& const_reference;
+ typedef T value_type;
+
+ // rebind
+ template <typename U>
+ struct rebind {
+ typedef alloc<U> other;
+ };
+
+ // address
+ pointer address( reference value ) const {
+ return &value;
+ }
+ const_pointer address( const_reference value ) const {
+ return &value;
+ }
+
+ // (trivial) ctors and dtors
+ alloc() {}
+ alloc( const alloc & ) {}
+ template <typename U> alloc( const alloc<U> & ) {}
+ // copy ctor is ok
+ ~alloc() {}
+
+ // de/allocation
+ size_type max_size() const {
+ return secmem_get_max_size();
+ }
+
+ pointer allocate( size_type n, void * =0 ) {
+ return static_cast<pointer>( secmem_malloc( n * sizeof(T) ) );
+ }
+
+ void deallocate( pointer p, size_type ) {
+ secmem_free( p );
+ }
+
+ // de/construct
+ void construct( pointer p, const T & value ) {
+ void * loc = p;
+ new (loc)T(value);
+ }
+ void destruct( pointer p ) {
+ p->~T();
+ }
+ };
+
+ // equality comparison
+ template <typename T1,typename T2>
+ bool operator==( const alloc<T1> &, const alloc<T2> & ) { return true; }
+ template <typename T1, typename T2>
+ bool operator!=( const alloc<T1> &, const alloc<T2> & ) { return false; }
+
+}
+
+#endif /* __SECMEM_SECMEMPP_H__ */
diff --git a/src/m_ver_check/VersionCheckingModule.cpp b/src/m_ver_check/VersionCheckingModule.cpp
index 947ae75..b674e8f 100644
--- a/src/m_ver_check/VersionCheckingModule.cpp
+++ b/src/m_ver_check/VersionCheckingModule.cpp
@@ -100,12 +100,9 @@ auto GFExecuteModule(GFModuleEvent* event) -> int {
auto* task = new VersionCheckTask();
QObject::connect(task, &VersionCheckTask::SignalUpgradeVersion,
QThread::currentThread(), [event](const SoftwareVersion&) {
- char** event_argv = static_cast<char**>(
- GFAllocateMemory(sizeof(char**) * 1));
- event_argv[0] = DUP("0");
-
- GFModuleTriggerModuleEventCallback(event, GFGetModuleID(),
- 1, event_argv);
+ GFModuleTriggerModuleEventCallback(
+ event, GFGetModuleID(), 1,
+ ConvertMapToParams({{"ret", "0"}}));
});
QObject::connect(task, &VersionCheckTask::SignalUpgradeVersion, task,
&QObject::deleteLater);