From c27c541257585878e4db705559c5cb953dc860b7 Mon Sep 17 00:00:00 2001 From: saturneric Date: Sun, 28 Jul 2024 10:18:33 +0200 Subject: [PATCH] feat: add pinentry module and paper key module --- include/GFModuleCommonUtils.hpp | 117 +++- src/CMakeLists.txt | 4 +- src/m_gpg_info/CMakeLists.txt | 3 - src/m_gpg_info/GnuPGInfoGatheringModule.cpp | 7 +- src/m_paper_key/CMakeLists.txt | 50 ++ src/m_paper_key/PaperKeyModule.cpp | 184 ++++++ src/m_paper_key/PaperKeyModule.h | 56 ++ src/m_paper_key/extract.cpp | 93 +++ src/m_paper_key/extract.h | 26 + src/m_paper_key/output.cpp | 304 ++++++++++ src/m_paper_key/output.h | 37 ++ src/m_paper_key/packets.cpp | 60 ++ src/m_paper_key/packets.h | 34 ++ src/m_paper_key/paperkey.cpp | 87 +++ src/m_paper_key/parse.cpp | 537 +++++++++++++++++ src/m_paper_key/parse.h | 37 ++ src/m_paper_key/restore.cpp | 181 ++++++ src/m_paper_key/restore.h | 27 + src/m_pinentry/CMakeLists.txt | 63 ++ src/m_pinentry/GpgPassphraseContext.cpp | 57 ++ src/m_pinentry/GpgPassphraseContext.h | 61 ++ src/m_pinentry/PinentryModule.cpp | 106 ++++ src/m_pinentry/PinentryModule.h | 56 ++ src/m_pinentry/RaisePinentry.cpp | 107 ++++ src/m_pinentry/RaisePinentry.h | 58 ++ src/m_pinentry/accessibility.cpp | 44 ++ src/m_pinentry/accessibility.h | 40 ++ src/m_pinentry/capslock/capslock.cpp | 41 ++ src/m_pinentry/capslock/capslock.h | 77 +++ src/m_pinentry/capslock/capslock_unix.cpp | 137 +++++ src/m_pinentry/capslock/capslock_win.cpp | 26 + src/m_pinentry/icons/data-error.svg | 9 + src/m_pinentry/icons/document-encrypt.png | Bin 0 -> 1835 bytes src/m_pinentry/icons/hint.svg | 13 + src/m_pinentry/icons/password-generate.svg | 13 + src/m_pinentry/icons/visibility.svg | 21 + src/m_pinentry/pinentry.cpp | 304 ++++++++++ src/m_pinentry/pinentry.h | 339 +++++++++++ src/m_pinentry/pinentry.qrc | 10 + src/m_pinentry/pinentry_debug.cpp | 31 + src/m_pinentry/pinentry_debug.h | 28 + src/m_pinentry/pinentryconfirm.cpp | 123 ++++ src/m_pinentry/pinentryconfirm.h | 63 ++ src/m_pinentry/pinentrydialog.cpp | 635 ++++++++++++++++++++ src/m_pinentry/pinentrydialog.h | 169 ++++++ src/m_pinentry/pinlineedit.cpp | 204 +++++++ src/m_pinentry/pinlineedit.h | 60 ++ src/m_pinentry/qti18n.cpp | 93 +++ src/m_pinentry/secmem++.h | 91 +++ src/m_ver_check/VersionCheckingModule.cpp | 9 +- 50 files changed, 4916 insertions(+), 16 deletions(-) create mode 100644 src/m_paper_key/CMakeLists.txt create mode 100644 src/m_paper_key/PaperKeyModule.cpp create mode 100644 src/m_paper_key/PaperKeyModule.h create mode 100644 src/m_paper_key/extract.cpp create mode 100644 src/m_paper_key/extract.h create mode 100644 src/m_paper_key/output.cpp create mode 100644 src/m_paper_key/output.h create mode 100644 src/m_paper_key/packets.cpp create mode 100644 src/m_paper_key/packets.h create mode 100644 src/m_paper_key/paperkey.cpp create mode 100644 src/m_paper_key/parse.cpp create mode 100644 src/m_paper_key/parse.h create mode 100644 src/m_paper_key/restore.cpp create mode 100644 src/m_paper_key/restore.h create mode 100644 src/m_pinentry/CMakeLists.txt create mode 100644 src/m_pinentry/GpgPassphraseContext.cpp create mode 100644 src/m_pinentry/GpgPassphraseContext.h create mode 100644 src/m_pinentry/PinentryModule.cpp create mode 100644 src/m_pinentry/PinentryModule.h create mode 100644 src/m_pinentry/RaisePinentry.cpp create mode 100644 src/m_pinentry/RaisePinentry.h create mode 100644 src/m_pinentry/accessibility.cpp create mode 100644 src/m_pinentry/accessibility.h create mode 100644 src/m_pinentry/capslock/capslock.cpp create mode 100644 src/m_pinentry/capslock/capslock.h create mode 100644 src/m_pinentry/capslock/capslock_unix.cpp create mode 100644 src/m_pinentry/capslock/capslock_win.cpp create mode 100644 src/m_pinentry/icons/data-error.svg create mode 100644 src/m_pinentry/icons/document-encrypt.png create mode 100644 src/m_pinentry/icons/hint.svg create mode 100644 src/m_pinentry/icons/password-generate.svg create mode 100644 src/m_pinentry/icons/visibility.svg create mode 100644 src/m_pinentry/pinentry.cpp create mode 100644 src/m_pinentry/pinentry.h create mode 100644 src/m_pinentry/pinentry.qrc create mode 100644 src/m_pinentry/pinentry_debug.cpp create mode 100644 src/m_pinentry/pinentry_debug.h create mode 100644 src/m_pinentry/pinentryconfirm.cpp create mode 100644 src/m_pinentry/pinentryconfirm.h create mode 100644 src/m_pinentry/pinentrydialog.cpp create mode 100644 src/m_pinentry/pinentrydialog.h create mode 100644 src/m_pinentry/pinlineedit.cpp create mode 100644 src/m_pinentry/pinlineedit.h create mode 100644 src/m_pinentry/qti18n.cpp create mode 100644 src/m_pinentry/secmem++.h 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 +#include #include #include @@ -113,6 +114,71 @@ inline auto QMapToGFModuleMetaDataList(const QMap& map) return head; } +inline auto ConvertEventParamsToMap(GFModuleEventParam* params) + -> QMap { + QMap 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& param_map) + -> GFModuleEventParam* { + GFModuleEventParam* head = nullptr; + GFModuleEventParam* prev = nullptr; + + for (const auto& [key, value] : param_map.asKeyValueRange()) { + auto* param = static_cast( + 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 { + QMap 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 event_map) + -> GFModuleEvent* { + auto* event = + static_cast(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(GFAllocateMemory(sizeof(char) * b.size())); memcpy(p, b.constData(), b.size()); @@ -136,6 +202,55 @@ auto SecureCreateSharedObject(Args&&... args) -> std::shared_ptr { } } +template +class PointerConverter { + public: + explicit PointerConverter(void* ptr) : ptr_(ptr) {} + + auto AsType() const -> T* { return static_cast(ptr_); } + + private: + void* ptr_; +}; + +/** + * @brief + * + * @tparam T + * @return T* + */ +template +auto SecureMallocAsType(std::size_t size) -> T* { + return PointerConverter(GFAllocateMemory(size)).AsType(); +} + +/** + * @brief + * + * @return void* + */ +template +auto SecureReallocAsType(T* ptr, std::size_t size) -> T* { + return PointerConverter(GFReallocateMemory(ptr, size)).AsType(); +} + +template +auto SecureCreateQSharedObject(Args&&... args) -> QSharedPointer { + void* mem = GFAllocateMemory(sizeof(T)); + if (!mem) throw std::bad_alloc(); + + try { + T* obj = new (mem) T(std::forward(args)...); + return QSharedPointer(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(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 +# +# 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 . +# +# 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 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 + * + * 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 . + * + * 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 starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "PaperKeyModule.h" + +#include + +#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 + * + * 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 . + * + * 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 starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include + +#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 + * + * 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 + +#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 + * + * 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 + +#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 + * + * 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 +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#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 + * + * 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 + +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 + * + * 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 +#include +#include + +#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( + GFReallocateMemory(packet->buf, packet->size)); + } + + memcpy(&packet->buf[packet->len], buf, len); + packet->len += len; + } else { + packet = + static_cast(GFAllocateMemory(sizeof(struct packet))); + packet->type = 0; + packet->buf = static_cast(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 + * + * 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 + +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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 +#include + +#include +#include +#include + +#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(GFAllocateMemory(sizeof(struct packet))); + packet->type = type; + packet->buf = static_cast(GFAllocateMemory(length)); + packet->len = length; + packet->size = length; + if (stream.readRawData(reinterpret_cast(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(GFAllocateMemory(sizeof(struct packet))); + packet->type = type; + packet->buf = static_cast(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(0x99)); + head.append(static_cast(public_len >> 8)); + head.append(static_cast(public_len & 0xFF)); + + sha.addData(head); + sha.addData(reinterpret_cast(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 + * + * 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 + +#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 + * + * 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 +#include +#include +#include +#include + +#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(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 + * + * 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 +# +# 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 . +# +# 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 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 + * + * 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 . + * + * 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 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 + * + * 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 . + * + * 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 starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include + +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 + * + * 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 . + * + * 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 starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "PinentryModule.h" + +#include +#include +#include + +#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( + 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 &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 + * + * 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 . + * + * 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 starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include + +#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 + * + * 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 . + * + * 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 starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "RaisePinentry.h" + +#include + +#include "GpgPassphraseContext.h" +#include "pinentrydialog.h" + +auto FindTopMostWindow(QWidget* fallback) -> QWidget* { + QList top_widgets = QApplication::topLevelWidgets(); + foreach (QWidget* widget, top_widgets) { + if (widget->isActiveWindow()) { + return widget; + } + } + return fallback; +} + +RaisePinentry::RaisePinentry(QWidget* parent, + QSharedPointer 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 + * + * 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 . + * + * 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 starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include + +class GpgPassphraseContext; + +class RaisePinentry : public QWidget { + Q_OBJECT + public: + /** + * @brief Construct a new Raise Pinentry object + * + * @param parent + */ + explicit RaisePinentry(QWidget *parent, QSharedPointer); + + /** + * @brief + * + * @return int + */ + auto Exec() -> int; + + signals: + + void SignalUserInputPassphraseCallback(QSharedPointer); + + private: + QSharedPointer 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 + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "accessibility.h" + +#include +#include + +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 + * + * 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 . + * 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 + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include + +#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 + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __PINENTRY_QT_CAPSLOCK_H__ +#define __PINENTRY_QT_CAPSLOCK_H__ + +#include +#include + +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 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 + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "capslock.h" + +#ifdef PINENTRY_QT_WAYLAND +#include +#include +#include +#include +#endif + +#include + +#ifdef PINENTRY_QT_X11 +#include + +#include +#undef Status +#endif + +#include + +#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 + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ +#include + +#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 @@ + + + + + diff --git a/src/m_pinentry/icons/document-encrypt.png b/src/m_pinentry/icons/document-encrypt.png new file mode 100644 index 0000000000000000000000000000000000000000..b80c2a6fe13898dba5e9a519a2c37f7b01a5972e GIT binary patch literal 1835 zcmV+`2h{k9P) z7Pv94Oze_{sf%umvS6tp?p)jWSh=BXU|~YE-~vGn2+_8bHeCCrz4xAT#_#)cnRDaC zK+~REndIZ2Gc#w-|NXC-a*cCNp5~U36xxj&H*(jnUw^SwD!sb7xyhj`v$M0;Mn^}> zvcql1JTN{!zQ0nbd=ltty{NbadA- z?e88vdUUqo1jfe3acN8tBygx) zE+2v%MqYH2;MJ>FWpHp%@IF2F?cu|R-)qtf&9^J;nI zz{tSBz^C#A?cBL@ANBO~e7wHCu8G~>-!F4>bMtF!YkfzK90_F$&2Et}Bp_)LVB+<&n$YT0o(EM+OUs*> z+KMY3lpX*aJja8N6^i-OpEdznipeNQN|ND6S5KTcaYwe+=!H9Ye}Q}5yLV6M4dm6K zvy)#wBED{&`O;QpVie>65wGq=_j|o*AEOq8l>7f z(3GiWt53SFkm2D7M<{m5vs6ZSF(j!p+aXp0Cc47}-&KpGl$;ZUoE`e?Dk=YIb z9zxq~Jdu5Cum}9dQ`#ea0*v2nHtzA%5?~H64AcLE=Etw!aWB2*q&VFzi^XQ#T_TYMmezAHB|eIegtV6EOqBpW3^;2vbT}DJmABq>VL?P3V1|7h`Cr7k|twcpTwwBtY2%Q2#8Ug96%X?8IZOI z7)Bw(#6(PJKs>U6#x-=;@bO7p%>nIOV<>@`-XTxTDGL}HCS4Es(3aRyQ9_ld!su93 zRlw=GEIp!1hatns;XH_;u+wLBeRUJjhjcw)Of8I}Hb3}e`L9Y^Kjd)L1KulDkc>u+ zLKLc_AI7sf%%K>=QB9>YNTHoDIByxe$2(ui5mk)2z`|=y_m#2vNC&gyxF( zRK{D`D-RjfctS_;o^FsPfmjuDfC(UhhYG}i4+YUcsyo5?F2|LG$_Plvi6Km5LMTaG zt1|HdQ5!18ujgr-37lUahE{Se*+(GJX$%2tA0n=~O4Je{p~MTxS}miLfcU(quG+Pq zm>9o+)Pg0Gq`YFV|5Or z6}?_Hq6*Q<3avi)Yz>j1E*H>);yisdd26l|n$SE0IIl3ANS^=`Q^Bg?@EBMx6BsXv`sS*Ly-W~2 zl#fA3oofkD>7c4~mU4$-tBQp?hzd|<|Gzw7>ycRfF|di^3aPWShkp+1Jy7o*!u?C( Ze*oBS#@$`k3m5 + + + + + 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 @@ + + + + + + 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 @@ + + + + + + + + + + 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "GFModuleCommonUtils.hpp" +#include "GFSDKBasic.h" + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifndef WINDOWS +#include +#endif +#include +#include +#include +#include +#include +#include +#ifndef WINDOWS +#include +#endif +#ifndef WINDOWS +#include +#endif +#include +#ifdef WINDOWS +#include +#endif + +#include +#include + +#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] ", 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(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(has_upper) + static_cast(has_lower) + + static_cast(has_digit) + static_cast(has_special); + score += variety_count * 10; + + for (auto i = 0; i < passphrase.length() - 1; ++i) { + if (passphrase[i] == passphrase[i + 1]) { + score -= 5; + } + } + + QHash 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(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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef PINENTRY_H +#define PINENTRY_H + +#include + +#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 @@ + + + + icons/data-error.svg + icons/document-encrypt.png + icons/hint.svg + icons/password-generate.svg + icons/visibility.svg + + \ 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 + * + * 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 . + * 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 + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __PINENTRY_QT_DEBUG_H__ +#define __PINENTRY_QT_DEBUG_H__ + +#include + +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 + * Copyright (C) 2022 g10 Code GmbH + * + * Software engineering by Ingo Klöcker + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "pinentryconfirm.h" + +#include +#include +#include +#include +#include +#include + +#include "accessibility.h" +#include "pinentrydialog.h" + +namespace { +QLabel *messageBoxLabel(QMessageBox *messageBox) { + return messageBox->findChild(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( + _timer.intervalAsDuration()); +} + +bool PinentryConfirm::timedOut() const { return _timed_out; } + +void PinentryConfirm::showEvent(QShowEvent *event) { + static bool resized; + if (!resized) { + QGridLayout *lay = dynamic_cast(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 + * Copyright (C) 2022 g10 Code GmbH + * + * Software engineering by Ingo Klöcker + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef PINENTRYCONFIRM_H +#define PINENTRYCONFIRM_H + +#include +#include +#include + +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 . + * Modified by Andre Heinecke + * Software engineering by Ingo Klöcker + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "pinentrydialog.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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("") + + options.hint.toHtmlEscaped() + + QLatin1String("")); + 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("") + + options.longHint.toHtmlEscaped().replace(QLatin1String("\n\n"), + QLatin1String("
")) + + QLatin1String("")); + 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(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 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 . + * Modified by Andre Heinecke + * Software engineering by Ingo Klöcker + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __PINENTRYDIALOG_H__ +#define __PINENTRYDIALOG_H__ + +#include +#include +#include +#include + +#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 _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 + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "pinlineedit.h" + +#include +#include +#include + +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 + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _PINLINEEDIT_H_ +#define _PINLINEEDIT_H_ + +#include + +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 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 + * SPDX-FileCopyrightText: 2021 Ingo Klöcker + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include + +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 + * + * 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 . + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __SECMEM_SECMEMPP_H__ +#define __SECMEM_SECMEMPP_H__ + +#include "../secmem/secmem.h" +#include + +namespace secmem { + + template + 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 + struct rebind { + typedef alloc 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 alloc( const alloc & ) {} + // 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( 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 + bool operator==( const alloc &, const alloc & ) { return true; } + template + bool operator!=( const alloc &, const alloc & ) { 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( - 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);