feat: add pinentry module and paper key module

This commit is contained in:
saturneric 2024-07-28 10:18:33 +02:00
parent 469eb2b4e0
commit c27c541257
50 changed files with 4916 additions and 16 deletions

View File

@ -29,6 +29,7 @@
#pragma once #pragma once
#include <GFSDKUI.h> #include <GFSDKUI.h>
#include <qsharedpointer.h>
#include <QMap> #include <QMap>
#include <QString> #include <QString>
@ -113,6 +114,71 @@ inline auto QMapToGFModuleMetaDataList(const QMap<QString, QString>& map)
return head; return head;
} }
inline auto ConvertEventParamsToMap(GFModuleEventParam* params)
-> QMap<QString, QString> {
QMap<QString, QString> param_map;
GFModuleEventParam* current = params;
GFModuleEventParam* last;
while (current != nullptr) {
param_map[current->name] = UDUP(current->value);
last = current;
current = current->next;
GFFreeMemory(last);
}
return param_map;
}
inline auto ConvertMapToParams(const QMap<QString, QString>& param_map)
-> GFModuleEventParam* {
GFModuleEventParam* head = nullptr;
GFModuleEventParam* prev = nullptr;
for (const auto& [key, value] : param_map.asKeyValueRange()) {
auto* param = static_cast<GFModuleEventParam*>(
GFAllocateMemory(sizeof(GFModuleEventParam)));
param->name = DUP(key.toUtf8());
param->value = DUP(value.toUtf8());
param->next = nullptr;
if (prev == nullptr) {
head = param;
} else {
prev->next = param;
}
prev = param;
}
return head;
}
inline auto ConvertEventToMap(GFModuleEvent* event) -> QMap<QString, QString> {
QMap<QString, QString> event_map;
event_map["event_id"] = UDUP(event->id);
event_map["trigger_id"] = UDUP(event->trigger_id);
event_map.insert(ConvertEventParamsToMap(event->params));
GFFreeMemory(event);
return event_map;
}
inline auto ConvertMapToEvent(QMap<QString, QString> event_map)
-> GFModuleEvent* {
auto* event =
static_cast<GFModuleEvent*>(GFAllocateMemory(sizeof(GFModuleEvent)));
event->id = DUP(event_map["event_id"].toUtf8());
event->trigger_id = DUP(event_map["trigger_id"].toUtf8());
event->params = nullptr;
return event;
}
inline auto AllocBufferAndCopy(const QByteArray& b) -> char* { inline auto AllocBufferAndCopy(const QByteArray& b) -> char* {
auto* p = static_cast<char*>(GFAllocateMemory(sizeof(char) * b.size())); auto* p = static_cast<char*>(GFAllocateMemory(sizeof(char) * b.size()));
memcpy(p, b.constData(), b.size()); memcpy(p, b.constData(), b.size());
@ -136,6 +202,55 @@ auto SecureCreateSharedObject(Args&&... args) -> std::shared_ptr<T> {
} }
} }
template <typename T>
class PointerConverter {
public:
explicit PointerConverter(void* ptr) : ptr_(ptr) {}
auto AsType() const -> T* { return static_cast<T*>(ptr_); }
private:
void* ptr_;
};
/**
* @brief
*
* @tparam T
* @return T*
*/
template <typename T>
auto SecureMallocAsType(std::size_t size) -> T* {
return PointerConverter<T>(GFAllocateMemory(size)).AsType();
}
/**
* @brief
*
* @return void*
*/
template <typename T>
auto SecureReallocAsType(T* ptr, std::size_t size) -> T* {
return PointerConverter<T>(GFReallocateMemory(ptr, size)).AsType();
}
template <typename T, typename... Args>
auto SecureCreateQSharedObject(Args&&... args) -> QSharedPointer<T> {
void* mem = GFAllocateMemory(sizeof(T));
if (!mem) throw std::bad_alloc();
try {
T* obj = new (mem) T(std::forward<Args>(args)...);
return QSharedPointer<T>(obj, [](T* ptr) {
ptr->~T();
GFFreeMemory(ptr);
});
} catch (...) {
GFFreeMemory(mem);
throw;
}
}
inline auto CharArrayToQStringList(char** pl_components, inline auto CharArrayToQStringList(char** pl_components,
int size) -> QStringList { int size) -> QStringList {
QStringList list; QStringList list;
@ -145,4 +260,4 @@ inline auto CharArrayToQStringList(char** pl_components,
} }
GFFreeMemory(pl_components); GFFreeMemory(pl_components);
return list; return list;
} }

View File

@ -25,4 +25,6 @@
# modules # modules
add_subdirectory(m_ver_check) add_subdirectory(m_ver_check)
add_subdirectory(m_gpg_info) add_subdirectory(m_gpg_info)
add_subdirectory(m_pinentry)
add_subdirectory(m_paper_key)

View File

@ -49,9 +49,6 @@ endif()
# using std c++ 17 # using std c++ 17
target_compile_features(mod_gpg_info PRIVATE cxx_std_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 # i18n
set(LOCALE_TS_PATH ${CMAKE_CURRENT_SOURCE_DIR}/ts) set(LOCALE_TS_PATH ${CMAKE_CURRENT_SOURCE_DIR}/ts)
set(TS_FILES "${LOCALE_TS_PATH}/ModuleGnuPGInfoGathering.en_US.ts" set(TS_FILES "${LOCALE_TS_PATH}/ModuleGnuPGInfoGathering.en_US.ts"

View File

@ -109,11 +109,8 @@ auto GFExecuteModule(GFModuleEvent *event) -> int {
StartGatheringGnuPGInfo(); StartGatheringGnuPGInfo();
char **event_argv = GFModuleTriggerModuleEventCallback(event, GFGetModuleID(), 1,
static_cast<char **>(GFAllocateMemory(sizeof(char **) * 1)); ConvertMapToParams({{"ret", "0"}}));
event_argv[0] = DUP("0");
GFModuleTriggerModuleEventCallback(event, GFGetModuleID(), 1, event_argv);
MLogDebug("gnupg external info gathering done"); MLogDebug("gnupg external info gathering done");
return 0; return 0;

View File

@ -0,0 +1,50 @@
# Copyright (C) 2021 Saturneric <eric@bktus.com>
#
# This file is part of GpgFrontend.
#
# GpgFrontend is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GpgFrontend is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
#
# The initial version of the source code is inherited from
# the gpg4usb project, which is under GPL-3.0-or-later.
#
# All the source code of GpgFrontend was modified and released by
# Saturneric <eric@bktus.com> 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)

View File

@ -0,0 +1,184 @@
/**
* Copyright (C) 2021 Saturneric <eric@bktus.com>
*
* This file is part of GpgFrontend.
*
* GpgFrontend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GpgFrontend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
*
* The initial version of the source code is inherited from
* the gpg4usb project, which is under GPL-3.0-or-later.
*
* All the source code of GpgFrontend was modified and released by
* Saturneric <eric@bktus.com> starting on May 12, 2021.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#include "PaperKeyModule.h"
#include <QtCore>
#include "GFModuleCommonUtils.hpp"
#include "GFSDKBuildInfo.h"
#include "extract.h"
auto GFGetModuleGFSDKVersion() -> const char * {
return DUP(GF_SDK_VERSION_STR);
}
auto GFGetModuleQtEnvVersion() -> const char * { return DUP(QT_VERSION_STR); }
auto GFGetModuleID() -> const char * {
return DUP("com.bktus.gpgfrontend.module.paper_key");
}
auto GFGetModuleVersion() -> const char * { return DUP("1.0.0"); }
auto GFGetModuleMetaData() -> GFModuleMetaData * {
return QMapToGFModuleMetaDataList(
{{"Name", "PaperKey"},
{"Description", "Integrated PaperKey Functions."},
{"Author", "Saturneric"}});
}
auto GFRegisterModule() -> int {
MLogDebug("paper key module registering");
return 0;
}
auto GFActiveModule() -> int {
MLogDebug("paper key module activating");
GFModuleListenEvent(GFGetModuleID(), DUP("REQUEST_TRANS_KEY_2_PAPER_KEY"));
GFModuleListenEvent(GFGetModuleID(), DUP("REQUEST_TRANS_PAPER_KEY_2_KEY"));
return 0;
}
auto GFExecuteModule(GFModuleEvent *p_event) -> int {
MLogDebug(
QString("paper key module executing, event id: %1").arg(p_event->id));
auto event = ConvertEventToMap(p_event);
if (event["event_id"] == "REQUEST_TRANS_KEY_2_PAPER_KEY") {
if (event["secret_key"].isEmpty() || event["output_path"].isEmpty()) {
GFModuleTriggerModuleEventCallback(
ConvertMapToEvent(event), GFGetModuleID(), 1,
ConvertMapToParams(
{{"ret", "-1"},
{"reason", "secret key or output path is empty"}}));
return -1;
}
QByteArray secret_key_data =
QByteArray::fromBase64(event["secret_key"].toUtf8());
QTemporaryFile secret_key_t_file;
if (!secret_key_t_file.open()) {
qWarning() << "Unable to open temporary file";
MLogWarn("unable to open temporary file");
return -1;
}
secret_key_t_file.write(secret_key_data);
secret_key_t_file.flush();
secret_key_t_file.seek(0);
FILE *file = fdopen(secret_key_t_file.handle(), "rb");
if (file == nullptr) {
qDebug() << "Unable to convert QTemporaryFile to FILE*";
return -1;
}
extract(file, event["output_path"].toUtf8(), AUTO);
fclose(file);
} else if (event["event_id"] == "REQUEST_TRANS_PAPER_KEY_2_KEY") {
if (event["public_key"].isEmpty() || event["paper_key_secrets"].isEmpty()) {
GFModuleTriggerModuleEventCallback(
ConvertMapToEvent(event), GFGetModuleID(), 1,
ConvertMapToParams(
{{"ret", "-1"},
{"reason", "public key or paper key secrets is empty"}}));
return -1;
}
QByteArray public_key_data =
QByteArray::fromBase64(event["public_key"].toUtf8());
QTemporaryFile public_key_t_file;
if (!public_key_t_file.open()) {
GFModuleTriggerModuleEventCallback(
ConvertMapToEvent(event), GFGetModuleID(), 1,
ConvertMapToParams(
{{"ret", "-1"}, {"reason", "unable to open temporary file"}}));
return -1;
}
public_key_t_file.write(public_key_data);
public_key_t_file.flush();
public_key_t_file.seek(0);
FILE *pubring = fdopen(public_key_t_file.handle(), "rb");
if (pubring == nullptr) {
GFModuleTriggerModuleEventCallback(
ConvertMapToEvent(event), GFGetModuleID(), 1,
ConvertMapToParams(
{{"ret", "-1"},
{"reason", "unable to convert QTemporaryFile to FILE*"}}));
return -1;
}
QByteArray secrets_data =
QByteArray::fromBase64(event["paper_key_secrets"].toUtf8());
QTemporaryFile secrets_data_file;
if (!secrets_data_file.open()) {
GFModuleTriggerModuleEventCallback(
ConvertMapToEvent(event), GFGetModuleID(), 1,
ConvertMapToParams(
{{"ret", "-1"}, {"reason", "unable to open temporary file"}}));
return -1;
}
secrets_data_file.write(public_key_data);
secrets_data_file.flush();
secrets_data_file.seek(0);
FILE *secrets = fdopen(secrets_data_file.handle(), "rb");
if (secrets == nullptr) {
GFModuleTriggerModuleEventCallback(
ConvertMapToEvent(event), GFGetModuleID(), 1,
ConvertMapToParams(
{{"ret", "-1"},
{"reason", "unable to convert QTemporaryFile to FILE*"}}));
return -1;
}
restore(pubring, secrets, AUTO, )
}
GFModuleTriggerModuleEventCallback(ConvertMapToEvent(event), GFGetModuleID(),
1, ConvertMapToParams({{"ret", "0"}}));
return 0;
}
auto GFDeactiveModule() -> int { return 0; }
auto GFUnregisterModule() -> int {
MLogDebug("paper key module unregistering");
return 0;
}

View File

@ -0,0 +1,56 @@
/**
* Copyright (C) 2021 Saturneric <eric@bktus.com>
*
* This file is part of GpgFrontend.
*
* GpgFrontend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GpgFrontend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
*
* The initial version of the source code is inherited from
* the gpg4usb project, which is under GPL-3.0-or-later.
*
* All the source code of GpgFrontend was modified and released by
* Saturneric <eric@bktus.com> starting on May 12, 2021.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#pragma once
#include <GFSDKModule.h>
#include "GFModuleExport.h"
extern "C" {
auto GF_MODULE_EXPORT GFGetModuleGFSDKVersion() -> const char *;
auto GF_MODULE_EXPORT GFGetModuleQtEnvVersion() -> const char *;
auto GF_MODULE_EXPORT GFGetModuleID() -> const char *;
auto GF_MODULE_EXPORT GFGetModuleVersion() -> const char *;
auto GF_MODULE_EXPORT GFGetModuleMetaData() -> GFModuleMetaData *;
auto GF_MODULE_EXPORT GFRegisterModule() -> int;
auto GF_MODULE_EXPORT GFActiveModule() -> int;
auto GF_MODULE_EXPORT GFExecuteModule(GFModuleEvent *) -> int;
auto GF_MODULE_EXPORT GFDeactiveModule() -> int;
auto GF_MODULE_EXPORT GFUnregisterModule() -> int;
};

View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2007, 2017 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "extract.h"
#include <cstdio>
#include "output.h"
#include "packets.h"
#include "parse.h"
extern int verbose;
int extract(FILE *input, const char *outname, enum data_type output_type) {
struct packet *packet;
int offset;
unsigned char fingerprint[20];
unsigned char version = 0;
packet = parse(input, 5, 0);
if (!packet) {
fprintf(stderr, "Unable to find secret key packet\n");
return 1;
}
offset = extract_secrets(packet);
if (offset == -1) return 1;
if (verbose > 1) fprintf(stderr, "Secret offset is %d\n", offset);
calculate_fingerprint(packet, offset, fingerprint);
if (verbose) {
fprintf(stderr, "Primary key fingerprint: ");
print_bytes(stderr, fingerprint, 20);
fprintf(stderr, "\n");
}
output_start(outname, output_type, fingerprint);
output_bytes(&version, 1);
output_bytes(packet->buf, 1);
output_bytes(fingerprint, 20);
output_length16(packet->len - offset);
output_bytes(&packet->buf[offset], packet->len - offset);
free_packet(packet);
while ((packet = parse(input, 7, 5))) {
offset = extract_secrets(packet);
if (offset == -1) return 1;
if (verbose > 1) fprintf(stderr, "Secret subkey offset is %d\n", offset);
calculate_fingerprint(packet, offset, fingerprint);
if (verbose) {
fprintf(stderr, "Subkey fingerprint: ");
print_bytes(stderr, fingerprint, 20);
fprintf(stderr, "\n");
}
output_bytes(packet->buf, 1);
output_bytes(fingerprint, 20);
output_length16(packet->len - offset);
output_bytes(&packet->buf[offset], packet->len - offset);
free_packet(packet);
}
output_finish();
if (input == stdin) {
/* Consume everything else on input */
while ((fgetc(input) != EOF));
}
return 0;
}

26
src/m_paper_key/extract.h Normal file
View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2007 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <cstdio>
#include "output.h"
auto extract(FILE *input, const char *outname,
enum data_type output_type) -> int;

304
src/m_paper_key/output.cpp Normal file
View File

@ -0,0 +1,304 @@
/*
* Copyright (C) 2007, 2008, 2009, 2012, 2016 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif
#include "output.h"
#include "packets.h"
extern unsigned int output_width;
extern char *comment;
static enum data_type output_type;
static FILE *output;
static unsigned int line_items;
static unsigned long all_crc = CRC24_INIT;
#define CRC24_POLY 0x864CFBL
void do_crc24(unsigned long *crc, const unsigned char *buf, size_t len) {
size_t i;
for (i = 0; i < len; i++) {
int j;
*crc ^= buf[i] << 16;
for (j = 0; j < 8; j++) {
*crc <<= 1;
if (*crc & 0x1000000) *crc ^= CRC24_POLY;
}
}
}
static void print_base16(const unsigned char *buf, size_t length) {
static unsigned long line_crc = CRC24_INIT;
static unsigned int line = 0;
if (buf) {
size_t i;
static unsigned int offset = 0;
for (i = 0; i < length; i++, offset++) {
if (offset % line_items == 0) {
if (line) {
fprintf(output, "%06lX\n", line_crc & 0xFFFFFFL);
line_crc = CRC24_INIT;
}
fprintf(output, "%3u: ", ++line);
}
fprintf(output, "%02X ", buf[i]);
do_crc24(&line_crc, &buf[i], 1);
}
} else {
fprintf(output, "%06lX\n", line_crc & 0xFFFFFFL);
fprintf(output, "%3u: %06lX\n", line + 1, all_crc & 0xFFFFFFL);
}
}
void print_bytes(FILE *stream, const unsigned char *buf, size_t length) {
size_t i;
for (i = 0; i < length; i++) fprintf(stream, "%02X", buf[i]);
}
void output_file_format(FILE *stream, const char *prefix) {
fprintf(stream, "%sFile format:\n", prefix);
fprintf(stream,
"%sa) 1 octet: Version of the paperkey format (currently 0).\n",
prefix);
fprintf(stream,
"%sb) 1 octet: OpenPGP key or subkey version (currently 4)\n",
prefix);
fprintf(stream,
"%sc) n octets: Key fingerprint (20 octets for a version 4 key or "
"subkey)\n",
prefix);
fprintf(
stream,
"%sd) 2 octets: 16-bit big endian length of the following secret data\n",
prefix);
fprintf(stream,
"%se) n octets: Secret data: a partial OpenPGP secret key or subkey "
"packet as\n",
prefix);
fprintf(stream,
"%s specified in RFC 4880, starting with the "
"string-to-key usage\n",
prefix);
fprintf(stream,
"%s octet and continuing until the end of the packet.\n",
prefix);
fprintf(stream,
"%sRepeat fields b through e as needed to cover all subkeys.\n",
prefix);
fprintf(stream, "%s\n", prefix);
fprintf(
stream,
"%sTo recover a secret key without using the paperkey program, use the\n",
prefix);
fprintf(stream,
"%skey fingerprint to match an existing public key packet with the\n",
prefix);
fprintf(stream,
"%scorresponding secret data from the paper key. Next, append this "
"secret\n",
prefix);
fprintf(stream,
"%sdata to the public key packet. Finally, switch the public key "
"packet tag\n",
prefix);
fprintf(stream,
"%sfrom 6 to 5 (14 to 7 for subkeys). This will recreate the "
"original secret\n",
prefix);
fprintf(stream,
"%skey or secret subkey packet. Repeat as needed for all public key "
"or subkey\n",
prefix);
fprintf(stream,
"%spackets in the public key. All other packets (user IDs, "
"signatures, etc.)\n",
prefix);
fprintf(stream, "%smay simply be copied from the public key.\n", prefix);
}
int output_start(const char *name, enum data_type type,
unsigned char fingerprint[20]) {
if (name) {
if (type == RAW)
output = fopen(name, "wb");
else
output = fopen(name, "w");
if (!output) return -1;
} else {
if (type == RAW) set_binary_mode(stdout);
output = stdout;
}
output_type = type;
switch (type) {
case RAW:
break;
case AUTO:
case BASE16: {
time_t now = time(NULL);
line_items = (output_width - 5 - 6) / 3;
fprintf(output, "# Secret portions of key ");
print_bytes(output, fingerprint, 20);
fprintf(output, "\n");
fprintf(output, "# Base16 data extracted %.24s\n", ctime(&now));
fprintf(output,
"# Created with "
"Paper Key Module of GpgFrontend"
" by Saturneric\n#\n");
output_file_format(output, "# ");
fprintf(output,
"#\n# Each base16 line ends with a CRC-24 of that line.\n");
fprintf(output,
"# The entire block of data ends with a CRC-24 of the entire "
"block of data.\n\n");
// if (comment != nullptr) fprintf(output, "# %s\n\n", comment);
} break;
}
return 0;
}
ssize_t output_bytes(const unsigned char *buf, size_t length) {
ssize_t ret = -1;
do_crc24(&all_crc, buf, length);
switch (output_type) {
case RAW:
if (buf == NULL) {
unsigned char crc[3];
crc[0] = (all_crc & 0xFFFFFFL) >> 16;
crc[1] = (all_crc & 0xFFFFFFL) >> 8;
crc[2] = (all_crc & 0xFFFFFFL);
ret = fwrite(crc, 1, 3, output);
} else
ret = fwrite(buf, 1, length, output);
break;
case AUTO:
case BASE16:
print_base16(buf, length);
ret = length;
break;
}
return ret;
}
ssize_t output_length16(size_t length) {
unsigned char encoded[2];
assert(length <= 65535);
encoded[0] = length >> 8;
encoded[1] = length;
return output_bytes(encoded, 2);
}
ssize_t output_openpgp_header(unsigned char tag, size_t length) {
unsigned char encoded[6];
size_t bytes;
/* We use the same "tag under 16, use old-style packets" rule that
many OpenPGP programs do. This helps make the resulting key
byte-for-byte identical. It's not a guarantee, as it is legal
for the generating program to use whatever packet style it likes,
but does help avoid questions why the input to paperkey might not
equal the output. */
if (tag < 16) {
if (length > 65535) {
encoded[0] = 0x80 | (tag << 2) | 2;
encoded[1] = length >> 24;
encoded[2] = length >> 16;
encoded[3] = length >> 8;
encoded[4] = length;
bytes = 5;
} else if (length > 255) {
encoded[0] = 0x80 | (tag << 2) | 1;
encoded[1] = length >> 8;
encoded[2] = length;
bytes = 3;
} else {
encoded[0] = 0x80 | (tag << 2);
encoded[1] = length;
bytes = 2;
}
} else {
encoded[0] = 0xC0 | tag;
if (length > 8383) {
encoded[1] = 0xFF;
encoded[2] = length >> 24;
encoded[3] = length >> 16;
encoded[4] = length >> 8;
encoded[5] = length;
bytes = 6;
} else if (length > 191) {
encoded[1] = 192 + ((length - 192) >> 8);
encoded[2] = (length - 192);
bytes = 3;
} else {
encoded[1] = length;
bytes = 2;
}
}
return output_bytes(encoded, bytes);
}
void output_finish(void) {
output_bytes(nullptr, 0);
if (output != nullptr && output != stdout) fclose(output);
}
void set_binary_mode(FILE *stream) {
#ifdef _WIN32
if (_setmode(_fileno(stream), _O_BINARY) == -1) {
fprintf(stderr, "Unable to set stream mode to binary: %s\n",
strerror(errno));
exit(1);
}
#else
(void)stream;
#endif
}

37
src/m_paper_key/output.h Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2007, 2012, 2016 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <cstdio>
enum data_type { AUTO, BASE16, RAW };
#define CRC24_INIT 0xB704CEL
void do_crc24(unsigned long *crc, const unsigned char *buf, size_t len);
void print_bytes(FILE *stream, const unsigned char *buf, size_t length);
void output_file_format(FILE *stream, const char *prefix);
int output_start(const char *name, enum data_type type,
unsigned char fingerprint[20]);
ssize_t output_bytes(const unsigned char *buf, size_t length);
#define output_packet(_packet) output_bytes((_packet)->buf, (_packet)->len)
ssize_t output_length16(size_t length);
ssize_t output_openpgp_header(unsigned char tag, size_t length);
void output_finish(void);
void set_binary_mode(FILE *stream);

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2007 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "packets.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "GFSDKBasic.h"
#include "output.h"
extern int verbose;
auto append_packet(struct packet *packet, unsigned char *buf,
size_t len) -> struct packet * {
if (packet != nullptr) {
while (packet->size - packet->len < len) {
packet->size += 100;
packet->buf = static_cast<unsigned char *>(
GFReallocateMemory(packet->buf, packet->size));
}
memcpy(&packet->buf[packet->len], buf, len);
packet->len += len;
} else {
packet =
static_cast<struct packet *>(GFAllocateMemory(sizeof(struct packet)));
packet->type = 0;
packet->buf = static_cast<unsigned char *>(GFAllocateMemory(len));
packet->len = len;
packet->size = len;
memcpy(packet->buf, buf, len);
}
return packet;
}
void free_packet(struct packet *packet) {
if (packet != nullptr) {
GFFreeMemory(packet->buf);
GFFreeMemory(packet);
}
}

34
src/m_paper_key/packets.h Normal file
View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2007 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <cstddef>
struct packet {
unsigned char type;
unsigned char *buf;
/* The length the data we've put into buf. */
size_t len;
/* The length we've malloced for buf. */
size_t size;
};
auto append_packet(struct packet *packet, unsigned char *buf,
size_t len) -> struct packet *;
void free_packet(struct packet *packet);

View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2007, 2008, 2009, 2012, 2016 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "extract.h"
#include "output.h"
int verbose = 0, ignore_crc_error = 0;
unsigned int output_width = 78;
char *comment = nullptr;
enum options {
OPT_HELP = 256,
OPT_VERSION,
OPT_VERBOSE,
OPT_OUTPUT,
OPT_INPUT_TYPE,
OPT_OUTPUT_TYPE,
OPT_OUTPUT_WIDTH,
OPT_SECRET_KEY,
OPT_PUBRING,
OPT_SECRETS,
OPT_IGNORE_CRC_ERROR,
OPT_FILE_FORMAT,
OPT_COMMENT
};
static struct option long_options[] = {
{"help", no_argument, NULL, OPT_HELP},
{"version", no_argument, NULL, OPT_VERSION},
{"verbose", no_argument, NULL, OPT_VERBOSE},
{"output", required_argument, NULL, OPT_OUTPUT},
{"input-type", required_argument, NULL, OPT_INPUT_TYPE},
{"output-type", required_argument, NULL, OPT_OUTPUT_TYPE},
{"output-width", required_argument, NULL, OPT_OUTPUT_WIDTH},
{"secret-key", required_argument, NULL, OPT_SECRET_KEY},
{"pubring", required_argument, NULL, OPT_PUBRING},
{"secrets", required_argument, NULL, OPT_SECRETS},
{"ignore-crc-error", no_argument, NULL, OPT_IGNORE_CRC_ERROR},
{"file-format", no_argument, NULL, OPT_FILE_FORMAT},
{"comment", required_argument, NULL, OPT_COMMENT},
{NULL, 0, NULL, 0}};
static void usage(void) {
printf("Usage: paperkey [OPTIONS]\n");
printf(" --help (-h)\n");
printf(" --version (-V)\n");
printf(" --verbose (-v) be more verbose\n");
printf(" --output (-o) write output to this file\n");
printf(" --input-type auto, base16 or raw (binary)\n");
printf(" --output-type base16 or raw (binary)\n");
printf(" --output-width maximum width of base16 output\n");
printf(
" --secret-key"
" extract secret data from this secret key\n");
printf(
" --pubring"
" public keyring to find non-secret data\n");
printf(
" --secrets file containing secret"
" data to join with the public key\n");
printf(" --ignore-crc-error don't reject corrupted input\n");
printf(" --file-format show the paperkey file format\n");
printf(" --comment add a comment to the base16 output\n");
}

537
src/m_paper_key/parse.cpp Normal file
View File

@ -0,0 +1,537 @@
/*
* Copyright (C) 2007, 2008, 2012, 2017 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "parse.h"
#include <qcryptographichash.h>
#include <qdatastream.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "GFSDKBasic.h"
#include "output.h"
#include "packets.h"
extern int verbose;
extern int ignore_crc_error;
struct packet *parse2(QDataStream &stream, unsigned char want,
unsigned char stop) {
int byte;
struct packet *packet = NULL;
while (!stream.atEnd()) {
stream >> byte;
unsigned char type;
unsigned int length;
if ((byte & 0x80) != 0) {
int tmp;
type = byte & 0x3F;
/* Old-style packet type */
if (!(byte & 0x40)) type >>= 2;
if (type == stop) {
stream << byte;
break;
}
if (byte & 0x40) {
/* New-style packets */
stream >> byte;
if (byte == EOF) goto fail;
if (byte == 255) {
/* 4-byte length */
stream >> tmp;
if (tmp == EOF) goto fail;
length = tmp << 24;
stream >> tmp;
if (tmp == EOF) goto fail;
length |= tmp << 16;
stream >> tmp;
if (tmp == EOF) goto fail;
length |= tmp << 8;
stream >> tmp;
if (tmp == EOF) goto fail;
length |= tmp;
} else if (byte >= 224) {
/* Partial body length, so fail (keys can't use
partial body) */
fprintf(stderr, "Invalid partial packet encoding\n");
goto fail;
} else if (byte >= 192) {
/* 2-byte length */
stream >> tmp;
if (tmp == EOF) goto fail;
length = ((byte - 192) << 8) + tmp + 192;
} else
length = byte;
} else {
/* Old-style packets */
switch (byte & 0x03) {
case 0:
/* 1-byte length */
stream >> byte;
if (byte == EOF) goto fail;
length = byte;
break;
case 1:
/* 2-byte length */
stream >> byte;
if (byte == EOF) goto fail;
stream >> tmp;
if (tmp == EOF) goto fail;
length = byte << 8;
length |= tmp;
break;
case 2:
/* 4-byte length */
stream >> tmp;
if (tmp == EOF) goto fail;
length = tmp << 24;
stream >> tmp;
if (tmp == EOF) goto fail;
length |= tmp << 16;
stream >> tmp;
if (tmp == EOF) goto fail;
length |= tmp << 8;
stream >> tmp;
if (tmp == EOF) goto fail;
length |= tmp;
break;
default:
fprintf(stderr, "Error: unable to parse old-style length\n");
goto fail;
}
}
if (verbose > 1)
fprintf(stderr, "Found packet of type %d, length %d\n", type, length);
} else {
fprintf(stderr,
"Error: unable to parse OpenPGP packets"
" (is this armored data?)\n");
goto fail;
}
if (want == 0 || type == want) {
packet =
static_cast<struct packet *>(GFAllocateMemory(sizeof(struct packet)));
packet->type = type;
packet->buf = static_cast<unsigned char *>(GFAllocateMemory(length));
packet->len = length;
packet->size = length;
if (stream.readRawData(reinterpret_cast<char *>(packet->buf),
packet->len) < packet->len) {
fprintf(stderr, "Short read on packet type %d\n", type);
goto fail;
}
break;
} else {
/* We don't want it, so skip the packet. We don't use fseek
here since the input might be on stdin and that isn't
seekable. */
size_t i;
for (i = 0; i < length; i++) stream >> byte;
}
}
return packet;
fail:
return nullptr;
}
struct packet *parse(FILE *input, unsigned char want, unsigned char stop) {
int byte;
struct packet *packet = NULL;
while ((byte = fgetc(input)) != EOF) {
unsigned char type;
unsigned int length;
if (byte & 0x80) {
int tmp;
type = byte & 0x3F;
/* Old-style packet type */
if (!(byte & 0x40)) type >>= 2;
if (type == stop) {
ungetc(byte, input);
break;
}
if (byte & 0x40) {
/* New-style packets */
byte = fgetc(input);
if (byte == EOF) goto fail;
if (byte == 255) {
/* 4-byte length */
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length = tmp << 24;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp << 16;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp << 8;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp;
} else if (byte >= 224) {
/* Partial body length, so fail (keys can't use
partial body) */
fprintf(stderr, "Invalid partial packet encoding\n");
goto fail;
} else if (byte >= 192) {
/* 2-byte length */
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length = ((byte - 192) << 8) + tmp + 192;
} else
length = byte;
} else {
/* Old-style packets */
switch (byte & 0x03) {
case 0:
/* 1-byte length */
byte = fgetc(input);
if (byte == EOF) goto fail;
length = byte;
break;
case 1:
/* 2-byte length */
byte = fgetc(input);
if (byte == EOF) goto fail;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length = byte << 8;
length |= tmp;
break;
case 2:
/* 4-byte length */
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length = tmp << 24;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp << 16;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp << 8;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp;
break;
default:
fprintf(stderr, "Error: unable to parse old-style length\n");
goto fail;
}
}
if (verbose > 1)
fprintf(stderr, "Found packet of type %d, length %d\n", type, length);
} else {
fprintf(stderr,
"Error: unable to parse OpenPGP packets"
" (is this armored data?)\n");
goto fail;
}
if (want == 0 || type == want) {
packet =
static_cast<struct packet *>(GFAllocateMemory(sizeof(struct packet)));
packet->type = type;
packet->buf = static_cast<unsigned char *>(GFAllocateMemory(length));
packet->len = length;
packet->size = length;
if (fread(packet->buf, 1, packet->len, input) < packet->len) {
fprintf(stderr, "Short read on packet type %d\n", type);
goto fail;
}
break;
} else {
/* We don't want it, so skip the packet. We don't use fseek
here since the input might be on stdin and that isn't
seekable. */
size_t i;
for (i = 0; i < length; i++) fgetc(input);
}
}
return packet;
fail:
return NULL;
}
int calculate_fingerprint(struct packet *packet, size_t public_len,
unsigned char fingerprint[20]) {
if (packet->buf[0] == 3) {
return -1;
} else if (packet->buf[0] == 4) {
QCryptographicHash sha(QCryptographicHash::Sha1);
QByteArray head;
head.append(static_cast<char>(0x99));
head.append(static_cast<char>(public_len >> 8));
head.append(static_cast<char>(public_len & 0xFF));
sha.addData(head);
sha.addData(reinterpret_cast<const char *>(packet->buf), public_len);
QByteArray result = sha.result();
if (result.size() != 20) {
return -1;
}
std::memcpy(fingerprint, result.constData(), 20);
}
return 0;
}
#define MPI_LENGTH(_start) (((((_start)[0] << 8 | (_start)[1]) + 7) / 8) + 2)
ssize_t extract_secrets(struct packet *packet) {
size_t offset;
if (packet->len == 0) return -1;
/* Secret keys consist of a public key with some secret material
stuck on the end. To get to the secrets, we have to skip the
public stuff. */
if (packet->buf[0] == 3) {
fprintf(stderr, "Version 3 (PGP 2.x style) keys are not supported.\n");
return -1;
} else if (packet->buf[0] == 4) {
/* Jump 5 bytes in. That gets us past 1 byte of version, and 4
bytes of timestamp. */
offset = 5;
} else
return -1;
if (packet->len <= offset) return -1;
switch (packet->buf[offset++]) {
case 1: /* RSA */
/* Skip 2 MPIs */
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
break;
case 16: /* Elgamal */
/* Skip 3 MPIs */
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
break;
case 17: /* DSA */
/* Skip 4 MPIs */
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
break;
case 18: /* ECDH */
/* Skip the curve ID and its length byte, plus an MPI, plus the
KDF parameters and their length byte */
offset += packet->buf[offset] + 1;
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += packet->buf[offset] + 1;
if (packet->len <= offset) return -1;
break;
case 19: /* ECDSA */
case 22: /* EdDSA - note that this is from an expired draft
https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-04,
but GnuPG is using algorithm 22 for it. */
/* Skip the curve ID and its length byte, plus an MPI */
offset += packet->buf[offset] + 1;
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
break;
default:
/* What algorithm? */
fprintf(stderr, "Unable to parse algorithm %u\n",
packet->buf[offset - 1]);
return -1;
}
return offset;
}
struct packet *read_secrets_file(FILE *secrets, enum data_type input_type) {
struct packet *packet = NULL;
int final_crc = 0;
unsigned long my_crc = 0;
if (input_type == RAW) {
unsigned char buffer[1024];
size_t got;
while ((got = fread(buffer, 1, 1024, secrets)))
packet = append_packet(packet, buffer, got);
if (got == 0 && !feof(secrets)) {
fprintf(stderr, "Error: unable to read secrets file\n");
free_packet(packet);
return NULL;
}
if (packet->len >= 3) {
/* Grab the last 3 bytes to be the CRC24 */
my_crc = packet->buf[packet->len - 3] << 16;
my_crc |= packet->buf[packet->len - 2] << 8;
my_crc |= packet->buf[packet->len - 1];
final_crc = 1;
packet->len -= 3;
}
} else {
char line[1024];
unsigned int next_linenum = 1;
while (fgets(line, 1024, secrets)) {
unsigned int linenum, did_digit = 0;
unsigned long line_crc = CRC24_INIT;
char *tok;
if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') continue;
linenum = atoi(line);
if (linenum != next_linenum) {
fprintf(stderr, "Error: missing line number %u (saw %u)\n",
next_linenum, linenum);
free_packet(packet);
return NULL;
} else
next_linenum = linenum + 1;
tok = strchr(line, ':');
if (tok) {
tok = strchr(tok, ' ');
while (tok) {
char *next;
while (*tok == ' ') tok++;
next = strchr(tok, ' ');
if (next == NULL) {
/* End of line, so check the CRC. */
unsigned long new_crc;
if (sscanf(tok, "%06lX", &new_crc)) {
if (did_digit) {
if ((new_crc & 0xFFFFFFL) != (line_crc & 0xFFFFFFL)) {
fprintf(stderr,
"CRC on line %d does not"
" match (%06lX!=%06lX)\n",
linenum, new_crc & 0xFFFFFFL, line_crc & 0xFFFFFFL);
if (!ignore_crc_error) {
free_packet(packet);
return NULL;
}
}
} else {
final_crc = 1;
my_crc = new_crc;
}
}
} else {
unsigned int digit;
if (sscanf(tok, "%02X", &digit)) {
unsigned char d = digit;
packet = append_packet(packet, &d, 1);
do_crc24(&line_crc, &d, 1);
did_digit = 1;
}
}
tok = next;
}
} else {
fprintf(stderr, "No colon ':' found in line %u\n", linenum);
free_packet(packet);
return NULL;
}
}
}
if (final_crc) {
unsigned long all_crc = CRC24_INIT;
do_crc24(&all_crc, packet->buf, packet->len);
if ((my_crc & 0xFFFFFFL) != (all_crc & 0xFFFFFFL)) {
fprintf(stderr, "CRC of secret does not match (%06lX!=%06lX)\n",
my_crc & 0xFFFFFFL, all_crc & 0xFFFFFFL);
if (!ignore_crc_error) {
free_packet(packet);
return NULL;
}
}
} else {
fprintf(stderr, "CRC of secret is missing\n");
if (!ignore_crc_error) {
free_packet(packet);
return NULL;
}
}
return packet;
}

37
src/m_paper_key/parse.h Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2007 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <qstring.h>
#include "output.h"
auto parse(FILE *input, unsigned char want,
unsigned char stop) -> struct packet *;
struct packet *parse2(QDataStream &stream, unsigned char want,
unsigned char stop);
auto calculate_fingerprint(struct packet *packet, size_t public_len,
unsigned char fingerprint[20]) -> int;
auto extract_secrets(struct packet *packet) -> ssize_t;
auto read_secrets_file(FILE *secrets,
enum data_type input_type) -> struct packet *;

181
src/m_paper_key/restore.cpp Normal file
View File

@ -0,0 +1,181 @@
/*
* Copyright (C) 2007, 2012 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "restore.h"
#include <cctype>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "GFSDKBasic.h"
#include "output.h"
#include "packets.h"
#include "parse.h"
struct key {
unsigned char fpr[20];
struct packet *packet;
struct key *next;
};
static auto extract_keys(struct packet *packet) -> struct key * {
struct key *key = NULL;
size_t idx = 1;
/* Check the version */
if (packet->len && packet->buf[0] != 0) {
fprintf(stderr, "Cannot handle secrets file version %d\n", packet->buf[0]);
return NULL;
}
while (idx < packet->len) {
/* 1+20+2 == version + fingerprint + length */
if (idx + 1 + 20 + 2 <= packet->len) {
if (packet->buf[idx] == 4) {
unsigned int len;
struct key *newkey;
newkey =
static_cast<struct key *>(GFAllocateMemory(sizeof(struct key)));
newkey->next = NULL;
idx++;
memcpy(newkey->fpr, &packet->buf[idx], 20);
idx += 20;
len = packet->buf[idx++] << 8;
len |= packet->buf[idx++];
if (idx + len <= packet->len) {
newkey->packet = append_packet(NULL, &packet->buf[idx], len);
idx += len;
} else {
fprintf(stderr, "Warning: Short data in secret image\n");
free(newkey);
break;
}
newkey->next = key;
key = newkey;
} else {
fprintf(stderr, "Warning: Corrupt data in secret image\n");
break;
}
} else {
fprintf(stderr, "Warning: Short header in secret image\n");
break;
}
}
return key;
}
static void free_keys(struct key *key) {
while (key) {
struct key *keytmp = key;
free_packet(key->packet);
key = key->next;
free(keytmp);
}
}
auto restore(FILE *pubring, FILE *secrets, enum data_type input_type,
const char *outname) -> int {
struct packet *secret;
if (input_type == AUTO) {
int test = fgetc(secrets);
if (test == EOF) {
fprintf(stderr, "Unable to check type of secrets file\n");
return 1;
} else if (isascii(test) && isprint(test))
input_type = BASE16;
else
input_type = RAW;
ungetc(test, secrets);
}
secret = read_secrets_file(secrets, input_type);
if (secret) {
struct packet *pubkey;
struct key *keys;
int did_pubkey = 0;
/* Build a list of all keys. We need to do this since the
public key we are transforming might have the subkeys in a
different order than (or not match subkeys at all with) our
secret data. */
keys = extract_keys(secret);
if (keys) {
output_start(outname, RAW, NULL);
while ((pubkey = parse(pubring, 0, 0))) {
unsigned char ptag;
if (pubkey->type == 6 || pubkey->type == 14) {
/* Public key or subkey */
unsigned char fpr[20];
struct key *keyidx;
if (pubkey->type == 6 && did_pubkey) break;
calculate_fingerprint(pubkey, pubkey->len, fpr);
/* Do we have a secret key that matches? */
for (keyidx = keys; keyidx; keyidx = keyidx->next) {
if (memcmp(fpr, keyidx->fpr, 20) == 0) {
if (pubkey->type == 6) {
ptag = 5;
did_pubkey = 1;
} else
ptag = 7;
/* Match, so create a secret key. */
output_openpgp_header(ptag, pubkey->len + keyidx->packet->len);
output_packet(pubkey);
output_packet(keyidx->packet);
}
}
} else if (did_pubkey) {
/* Copy the usual user ID, sigs, etc, so the key is
well-formed. */
output_openpgp_header(pubkey->type, pubkey->len);
output_packet(pubkey);
}
free_packet(pubkey);
}
free_keys(keys);
} else {
fprintf(stderr, "Unable to parse secret data\n");
return 1;
}
} else {
fprintf(stderr, "Unable to read secrets file\n");
return 1;
}
return 0;
}

27
src/m_paper_key/restore.h Normal file
View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2007 David Shaw <dshaw@jabberwocky.com>
*
* 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_ */

View File

@ -0,0 +1,63 @@
# Copyright (C) 2021 Saturneric <eric@bktus.com>
#
# This file is part of GpgFrontend.
#
# GpgFrontend is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GpgFrontend is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
#
# The initial version of the source code is inherited from
# the gpg4usb project, which is under GPL-3.0-or-later.
#
# All the source code of GpgFrontend was modified and released by
# Saturneric <eric@bktus.com> 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)

View File

@ -0,0 +1,57 @@
/**
* Copyright (C) 2021 Saturneric <eric@bktus.com>
*
* This file is part of GpgFrontend.
*
* GpgFrontend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GpgFrontend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
*
* The initial version of the source code is inherited from
* the gpg4usb project, which is under GPL-3.0-or-later.
*
* All the source code of GpgFrontend was modified and released by
* Saturneric <eric@bktus.com> 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_; }

View File

@ -0,0 +1,61 @@
/**
* Copyright (C) 2021 Saturneric <eric@bktus.com>
*
* This file is part of GpgFrontend.
*
* GpgFrontend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GpgFrontend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
*
* The initial version of the source code is inherited from
* the gpg4usb project, which is under GPL-3.0-or-later.
*
* All the source code of GpgFrontend was modified and released by
* Saturneric <eric@bktus.com> starting on May 12, 2021.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#pragma once
#include <QObject>
class GpgPassphraseContext : public QObject {
Q_OBJECT
public:
GpgPassphraseContext(const QString& uids_info, const QString& passphrase_info,
bool prev_was_bad, bool ask_for_new);
GpgPassphraseContext();
void SetPassphrase(const QString& passphrase);
[[nodiscard]] auto GetPassphrase() const -> QString;
[[nodiscard]] auto GetUidsInfo() const -> QString;
[[nodiscard]] auto GetPassphraseInfo() const -> QString;
[[nodiscard]] auto IsPreWasBad() const -> bool;
[[nodiscard]] auto IsAskForNew() const -> bool;
private:
QString passphrase_info_;
QString uids_info_;
QString passphrase_;
bool prev_was_bad_;
bool ask_for_new_;
};

View File

@ -0,0 +1,106 @@
/**
* Copyright (C) 2021 Saturneric <eric@bktus.com>
*
* This file is part of GpgFrontend.
*
* GpgFrontend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GpgFrontend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
*
* The initial version of the source code is inherited from
* the gpg4usb project, which is under GPL-3.0-or-later.
*
* All the source code of GpgFrontend was modified and released by
* Saturneric <eric@bktus.com> starting on May 12, 2021.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#include "PinentryModule.h"
#include <qapplication.h>
#include <qobjectdefs.h>
#include <qthread.h>
#include "GFModuleCommonUtils.hpp"
#include "GFSDKBuildInfo.h"
#include "GpgPassphraseContext.h"
#include "RaisePinentry.h"
auto GFGetModuleGFSDKVersion() -> const char * {
return DUP(GF_SDK_VERSION_STR);
}
auto GFGetModuleQtEnvVersion() -> const char * { return DUP(QT_VERSION_STR); }
auto GFGetModuleID() -> const char * {
return DUP("com.bktus.gpgfrontend.module.pinentry");
}
auto GFGetModuleVersion() -> const char * { return DUP("1.0.0"); }
auto GFGetModuleMetaData() -> GFModuleMetaData * {
return QMapToGFModuleMetaDataList({{"Name", "Pinentry"},
{"Description", "A simple tiny pinentry."},
{"Author", "Saturneric"}});
}
auto GFRegisterModule() -> int {
MLogDebug("pinentry module registering");
return 0;
}
auto GFActiveModule() -> int {
MLogDebug("pinentry module activating");
GFModuleListenEvent(GFGetModuleID(), DUP("REQUEST_PIN_ENTRY"));
return 0;
}
auto GFExecuteModule(GFModuleEvent *p_event) -> int {
MLogDebug(
QString("pinentry module executing, event id: %1").arg(p_event->id));
auto event = ConvertEventToMap(p_event);
QMetaObject::invokeMethod(
QApplication::instance()->thread(), [p_event, event]() -> int {
auto *p = new RaisePinentry(
nullptr,
SecureCreateQSharedObject<GpgPassphraseContext>(
event["uid_hint"], event["passphrase_info"],
event["prev_was_bad"].toInt(), event["ask_for_new"].toInt()));
QObject::connect(
p, &RaisePinentry::SignalUserInputPassphraseCallback, p,
[event](const QSharedPointer<GpgPassphraseContext> &c) {
GFModuleTriggerModuleEventCallback(
ConvertMapToEvent(event), GFGetModuleID(), 1,
ConvertMapToParams({{"passphrase", c->GetPassphrase()}}));
});
p->Exec();
return 0;
});
return 0;
}
auto GFDeactiveModule() -> int { return 0; }
auto GFUnregisterModule() -> int {
MLogDebug("pinentry module unregistering");
return 0;
}

View File

@ -0,0 +1,56 @@
/**
* Copyright (C) 2021 Saturneric <eric@bktus.com>
*
* This file is part of GpgFrontend.
*
* GpgFrontend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GpgFrontend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
*
* The initial version of the source code is inherited from
* the gpg4usb project, which is under GPL-3.0-or-later.
*
* All the source code of GpgFrontend was modified and released by
* Saturneric <eric@bktus.com> starting on May 12, 2021.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#pragma once
#include <GFSDKModule.h>
#include "GFModuleExport.h"
extern "C" {
auto GF_MODULE_EXPORT GFGetModuleGFSDKVersion() -> const char *;
auto GF_MODULE_EXPORT GFGetModuleQtEnvVersion() -> const char *;
auto GF_MODULE_EXPORT GFGetModuleID() -> const char *;
auto GF_MODULE_EXPORT GFGetModuleVersion() -> const char *;
auto GF_MODULE_EXPORT GFGetModuleMetaData() -> GFModuleMetaData *;
auto GF_MODULE_EXPORT GFRegisterModule() -> int;
auto GF_MODULE_EXPORT GFActiveModule() -> int;
auto GF_MODULE_EXPORT GFExecuteModule(GFModuleEvent *) -> int;
auto GF_MODULE_EXPORT GFDeactiveModule() -> int;
auto GF_MODULE_EXPORT GFUnregisterModule() -> int;
};

View File

@ -0,0 +1,107 @@
/**
* Copyright (C) 2021 Saturneric <eric@bktus.com>
*
* This file is part of GpgFrontend.
*
* GpgFrontend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GpgFrontend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
*
* The initial version of the source code is inherited from
* the gpg4usb project, which is under GPL-3.0-or-later.
*
* All the source code of GpgFrontend was modified and released by
* Saturneric <eric@bktus.com> starting on May 12, 2021.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#include "RaisePinentry.h"
#include <qapplication.h>
#include "GpgPassphraseContext.h"
#include "pinentrydialog.h"
auto FindTopMostWindow(QWidget* fallback) -> QWidget* {
QList<QWidget*> top_widgets = QApplication::topLevelWidgets();
foreach (QWidget* widget, top_widgets) {
if (widget->isActiveWindow()) {
return widget;
}
}
return fallback;
}
RaisePinentry::RaisePinentry(QWidget* parent,
QSharedPointer<GpgPassphraseContext> context)
: QWidget(parent), context_(std::move(context)) {}
auto RaisePinentry::Exec() -> int {
bool ask_for_new = context_->IsAskForNew() &&
context_->GetPassphraseInfo().isEmpty() &&
context_->GetUidsInfo().isEmpty();
auto* pinentry =
new PinEntryDialog(FindTopMostWindow(this), 0, 15, true, ask_for_new,
ask_for_new ? tr("Repeat Passphrase:") : QString(),
tr("Show passphrase"), tr("Hide passphrase"));
if (context_->IsPreWasBad()) {
pinentry->setError(tr("Given Passphrase was wrong. Please retry."));
}
pinentry->setPrompt(tr("Passphrase:"));
if (!context_->GetUidsInfo().isEmpty()) {
pinentry->setDescription(QString("Please provide Passphrase of Key:\n%1\n")
.arg(context_->GetUidsInfo()));
}
struct pinentry pinentry_info;
pinentry->setPinentryInfo(pinentry_info);
pinentry->setRepeatErrorText(tr("Passphrases do not match"));
pinentry->setGenpinLabel(QString(""));
pinentry->setGenpinTT(QString(""));
pinentry->setCapsLockHint(tr("Caps Lock is on"));
pinentry->setFormattedPassphrase({false, QString()});
pinentry->setConstraintsOptions({false, QString(), QString(), QString()});
pinentry->setWindowTitle(tr("Bundled Pinentry"));
/* If we reuse the same dialog window. */
pinentry->setPin(QString());
pinentry->setOkText(tr("Confirm"));
pinentry->setCancelText(tr("Cancel"));
connect(pinentry, &PinEntryDialog::finished, this,
[pinentry, this](int result) {
bool ret = result != 0;
if (!ret) {
emit SignalUserInputPassphraseCallback({});
return -1;
}
auto pin = pinentry->pin().toUtf8();
context_->SetPassphrase(pin);
emit SignalUserInputPassphraseCallback(context_);
return 0;
});
connect(pinentry, &PinEntryDialog::finished, this, &QWidget::deleteLater);
pinentry->open();
return 0;
}

View File

@ -0,0 +1,58 @@
/**
* Copyright (C) 2021 Saturneric <eric@bktus.com>
*
* This file is part of GpgFrontend.
*
* GpgFrontend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GpgFrontend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
*
* The initial version of the source code is inherited from
* the gpg4usb project, which is under GPL-3.0-or-later.
*
* All the source code of GpgFrontend was modified and released by
* Saturneric <eric@bktus.com> starting on May 12, 2021.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#pragma once
#include <QWidget>
class GpgPassphraseContext;
class RaisePinentry : public QWidget {
Q_OBJECT
public:
/**
* @brief Construct a new Raise Pinentry object
*
* @param parent
*/
explicit RaisePinentry(QWidget *parent, QSharedPointer<GpgPassphraseContext>);
/**
* @brief
*
* @return int
*/
auto Exec() -> int;
signals:
void SignalUserInputPassphraseCallback(QSharedPointer<GpgPassphraseContext>);
private:
QSharedPointer<GpgPassphraseContext> context_;
};

View File

@ -0,0 +1,44 @@
/* accessibility.cpp - Helpers for making pinentry accessible
* Copyright (C) 2021 g10 Code GmbH
*
* Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#include "accessibility.h"
#include <QString>
#include <QWidget>
namespace Accessibility {
void setDescription(QWidget *w, const QString &text) {
if (w) {
#ifndef QT_NO_ACCESSIBILITY
w->setAccessibleDescription(text);
#endif
}
}
void setName(QWidget *w, const QString &text) {
if (w) {
#ifndef QT_NO_ACCESSIBILITY
w->setAccessibleName(text);
#endif
}
}
} // namespace Accessibility

View File

@ -0,0 +1,40 @@
/* accessibility.h - Helpers for making pinentry accessible
* Copyright (C) 2021 g10 Code GmbH
*
* Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef __PINENTRY_QT_ACCESSIBILITY_H__
#define __PINENTRY_QT_ACCESSIBILITY_H__
class QString;
class QWidget;
namespace Accessibility
{
/* Wrapper for QWidget::setAccessibleDescription which does nothing if
QT_NO_ACCESSIBILITY is defined. */
void setDescription(QWidget *w, const QString &text);
/* Wrapper for QWidget::setAccessibleName which does nothing if
QT_NO_ACCESSIBILITY is defined. */
void setName(QWidget *w, const QString &text);
} // namespace Accessibility
#endif // __PINENTRY_QT_ACCESSIBILITY_H__

View File

@ -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 <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#include <QDebug>
#include <QGuiApplication>
#include "capslock.h"
CapsLockWatcher::Private::Private(CapsLockWatcher *q) : q{q} {
#ifdef PINENTRY_QT_WAYLAND
if (qApp->platformName() == QLatin1String("wayland")) {
watchWayland();
}
#endif
}
CapsLockWatcher::CapsLockWatcher(QObject *parent)
: QObject{parent}, d{new Private{this}} {
if (qApp->platformName() == QLatin1String("wayland")) {
#ifndef PINENTRY_QT_WAYLAND
qWarning() << "CapsLockWatcher was compiled without support for Wayland";
#endif
}
}

View File

@ -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 <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef __PINENTRY_QT_CAPSLOCK_H__
#define __PINENTRY_QT_CAPSLOCK_H__
#include <QObject>
#include <memory>
enum class LockState { Unknown = -1, Off, On };
LockState capsLockState();
#ifdef PINENTRY_QT_WAYLAND
namespace KWayland {
namespace Client {
class Registry;
class Seat;
} // namespace Client
} // namespace KWayland
#endif
class CapsLockWatcher : public QObject {
Q_OBJECT
public:
explicit CapsLockWatcher(QObject *parent = nullptr);
Q_SIGNALS:
void stateChanged(bool locked);
private:
class Private;
std::unique_ptr<Private> d;
};
class CapsLockWatcher::Private {
public:
explicit Private(CapsLockWatcher *);
#ifdef PINENTRY_QT_WAYLAND
void watchWayland();
#endif
private:
#ifdef PINENTRY_QT_WAYLAND
void registry_seatAnnounced(quint32, quint32);
void seat_hasKeyboardChanged(bool);
void keyboard_modifiersChanged(quint32);
#endif
private:
CapsLockWatcher *const q;
#ifdef PINENTRY_QT_WAYLAND
KWayland::Client::Registry *registry = nullptr;
KWayland::Client::Seat *seat = nullptr;
#endif
};
#endif // __PINENTRY_QT_CAPSLOCK_H__

View File

@ -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 <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "capslock.h"
#ifdef PINENTRY_QT_WAYLAND
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/keyboard.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/seat.h>
#endif
#include <QGuiApplication>
#ifdef PINENTRY_QT_X11
#include <X11/XKBlib.h>
#include <QX11Info>
#undef Status
#endif
#include <QDebug>
#ifdef PINENTRY_QT_WAYLAND
using namespace KWayland::Client;
#endif
#ifdef PINENTRY_QT_WAYLAND
static bool watchingWayland = false;
#endif
LockState capsLockState() {
static bool reportUnsupportedPlatform = true;
#ifdef PINENTRY_QT_X11
if (qApp->platformName() == QLatin1String("xcb")) {
unsigned int state;
XkbGetIndicatorState(QX11Info::display(), XkbUseCoreKbd, &state);
return (state & 0x01) == 1 ? LockState::On : LockState::Off;
}
#endif
#ifdef PINENTRY_QT_WAYLAND
if (qApp->platformName() == QLatin1String("wayland")) {
if (!watchingWayland && reportUnsupportedPlatform) {
qDebug() << "Use CapsLockWatcher for checking for Caps Lock on Wayland";
}
} else
#endif
if (reportUnsupportedPlatform) {
qWarning() << "Checking for Caps Lock not possible on unsupported platform:"
<< qApp->platformName();
}
reportUnsupportedPlatform = false;
return LockState::Unknown;
}
#ifdef PINENTRY_QT_WAYLAND
void CapsLockWatcher::Private::watchWayland() {
watchingWayland = true;
auto connection = ConnectionThread::fromApplication(q);
if (!connection) {
qWarning() << "Failed to get connection to Wayland server from QPA";
return;
}
registry = new Registry{q};
registry->create(connection);
if (!registry->isValid()) {
qWarning() << "Failed to create valid KWayland registry";
return;
}
registry->setup();
connect(registry, &Registry::seatAnnounced, q,
[this](quint32 name, quint32 version) {
registry_seatAnnounced(name, version);
});
}
void CapsLockWatcher::Private::registry_seatAnnounced(quint32 name,
quint32 version) {
Q_ASSERT(registry);
seat = registry->createSeat(name, version, q);
if (!seat->isValid()) {
qWarning() << "Failed to create valid KWayland seat";
return;
}
connect(seat, &Seat::hasKeyboardChanged, q,
[this](bool hasKeyboard) { seat_hasKeyboardChanged(hasKeyboard); });
}
void CapsLockWatcher::Private::seat_hasKeyboardChanged(bool hasKeyboard) {
Q_ASSERT(seat);
if (!hasKeyboard) {
qDebug() << "Seat has no keyboard";
return;
}
auto keyboard = seat->createKeyboard(q);
if (!keyboard->isValid()) {
qWarning() << "Failed to create valid KWayland keyboard";
return;
}
connect(keyboard, &Keyboard::modifiersChanged, q,
[this](quint32, quint32, quint32 locked, quint32) {
keyboard_modifiersChanged(locked);
});
}
void CapsLockWatcher::Private::keyboard_modifiersChanged(quint32 locked) {
const bool capsLockIsLocked = (locked & 2u) != 0;
qDebug() << "Caps Lock is locked:" << capsLockIsLocked;
Q_EMIT q->stateChanged(capsLockIsLocked);
}
#endif

View File

@ -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 <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#include <windows.h>
#include "capslock.h"
LockState capsLockState() {
return (GetKeyState(VK_CAPITAL) & 1) ? LockState::On : LockState::Off;
}

View File

@ -0,0 +1,9 @@
<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
<style type="text/css" id="current-color-scheme">
.ColorScheme-NegativeText {
color:#da4453;
}
</style>
<rect class="ColorScheme-NegativeText" x="3" y="3" width="16" height="16" rx="2" fill="currentColor"/>
<path d="M 6.414,5 5,6.414 9.586,11 5,15.586 6.414,17 11,12.414 15.586,17 17,15.586 12.414,11 17,6.414 15.586,5 11,9.586 Z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 13.314453 2 L 2 13.294922 L 2.7148438 14 L 14 2.6972656 L 13.314453 2 z M 8 3 A 8.9999916 9.000003 0 0 0 0.12304688 7.6679688 C 0.25199187 8.0317035 0.48048562 8.3445563 0.77929688 8.5761719 A 7.9999926 8.0000028 0 0 1 8 4 A 3.9999993 4.0000007 0 0 0 4 8 A 3.9999993 4.0000007 0 0 0 4.1054688 8.8945312 L 5 8 A 2.9999993 3.0000005 0 0 1 8 5 L 8.8925781 4.1074219 A 3.9999993 4.0000007 0 0 0 8.3496094 4.0175781 A 7.9999926 8.0000028 0 0 1 8.9277344 4.0722656 L 9.8066406 3.1933594 A 8.9999916 9.000003 0 0 0 8 3 z M 13.835938 5.1640625 L 13.121094 5.8789062 A 7.9999926 8.0000028 0 0 1 15.220703 8.5761719 C 15.522218 8.3424607 15.752612 8.0261216 15.880859 7.6582031 A 8.9999916 9.000003 0 0 0 13.835938 5.1640625 z M 11.894531 7.1054688 L 11 8 A 2.9999993 3.0000005 0 0 1 8 11 L 7.1074219 11.892578 A 3.9999993 4.0000007 0 0 0 8 12 A 3.9999993 4.0000007 0 0 0 12 8 A 3.9999993 4.0000007 0 0 0 11.894531 7.1054688 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="m3.5 2l-.531.969-.969.531.969.531.531.969.531-.969.969-.531-.969-.531zm7.631 0l-9.125 9.125 2.875 2.875 9.125-9.125zm0 1.438l1.438 1.439-2.781 2.779-1.438-1.438z"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<g
transform="translate(-421.71429,-531.79074)">
<g
transform="matrix(0.75,0,0,0.74999813,421.46429,-241.22897)">
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 8 3 A 8.9999925 9.0000023 0 0 0 0.12304688 7.6679688 C 0.2519919 8.0317178 0.48048563 8.3445725 0.77929688 8.5761719 A 7.9999935 8.0000021 0 0 1 8 4 A 3.9999996 4.0000004 0 0 0 4 8 A 3.9999996 4.0000004 0 0 0 8 12 A 3.9999996 4.0000004 0 0 0 12 8 A 3.9999996 4.0000004 0 0 0 8.3496094 4.0175781 A 7.9999935 8.0000021 0 0 1 15.220703 8.5761719 C 15.522218 8.3424725 15.752612 8.0260772 15.880859 7.6582031 A 8.9999925 9.0000023 0 0 0 8 3 z M 8 5 A 2.9999996 3.0000002 0 0 1 11 8 A 2.9999996 3.0000002 0 0 1 8 11 A 2.9999996 3.0000002 0 0 1 5 8 A 2.9999996 3.0000002 0 0 1 8 5 z M 8 6 A 1.9999999 2.0000003 0 0 0 6 8 A 1.9999999 2.0000003 0 0 0 8 10 A 1.9999999 2.0000003 0 0 0 10 8 A 1.9999999 2.0000003 0 0 0 9.9101562 7.4121094 A 0.9999999 1 0 0 1 9 8 A 0.9999999 1 0 0 1 8 7 A 0.9999999 1 0 0 1 8.5898438 6.0898438 A 1.9999999 2.0000003 0 0 0 8 6 z "
transform="matrix(1.3333333,0,0,1.3333367,0.33333333,1030.6955)"
class="ColorScheme-Text"
id="rect4170" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

304
src/m_pinentry/pinentry.cpp Normal file
View File

@ -0,0 +1,304 @@
/* pinentry.c - The PIN entry support library
* Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015, 2016, 2021 g10 Code GmbH
*
* This file is part of PINENTRY.
*
* PINENTRY is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* PINENTRY is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#include "GFModuleCommonUtils.hpp"
#include "GFSDKBasic.h"
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifndef WINDOWS
#include <errno.h>
#endif
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef WINDOWS
#include <sys/utsname.h>
#endif
#ifndef WINDOWS
#include <locale.h>
#endif
#include <limits.h>
#ifdef WINDOWS
#include <windows.h>
#endif
#include <assuan.h>
#include <qhash.h>
#include "pinentry.h"
#ifdef WINDOWS
#define getpid() GetCurrentProcessId()
#endif
/* Keep the name of our program here. */
static char this_pgmname[50];
struct pinentry pinentry;
static const char *flavor_flag;
/* Return a malloced copy of the commandline for PID. If this is not
* possible NULL is returned. */
#ifndef WINDOWS
static char *get_cmdline(unsigned long pid) {
char buffer[200];
FILE *fp;
size_t i, n;
snprintf(buffer, sizeof buffer, "/proc/%lu/cmdline", pid);
fp = fopen(buffer, "rb");
if (!fp) return NULL;
n = fread(buffer, 1, sizeof buffer - 1, fp);
if (n < sizeof buffer - 1 && ferror(fp)) {
/* Some error occurred. */
fclose(fp);
return NULL;
}
fclose(fp);
if (n == 0) return NULL;
/* Arguments are delimited by Nuls. We should do proper quoting but
* that can be a bit complicated, thus we simply replace the Nuls by
* spaces. */
for (i = 0; i < n; i++)
if (!buffer[i] && i < n - 1) buffer[i] = ' ';
buffer[i] = 0; /* Make sure the last byte is the string terminator. */
return strdup(buffer);
}
#endif /*!WINDOWS*/
/* Atomically ask the kernel for information about process PID.
* Return a malloc'ed copy of the process name as long as the process
* uid matches UID. If it cannot determine that the process has uid
* UID, it returns NULL.
*
* This is not as informative as get_cmdline, but it verifies that the
* process does belong to the user in question.
*/
#ifndef WINDOWS
static char *get_pid_name_for_uid(unsigned long pid, int uid) {
char buffer[400];
FILE *fp;
size_t end, n;
char *uidstr;
snprintf(buffer, sizeof buffer, "/proc/%lu/status", pid);
fp = fopen(buffer, "rb");
if (!fp) return NULL;
n = fread(buffer, 1, sizeof buffer - 1, fp);
if (n < sizeof buffer - 1 && ferror(fp)) {
/* Some error occurred. */
fclose(fp);
return NULL;
}
fclose(fp);
if (n == 0) return NULL;
buffer[n] = 0;
/* Fixme: Is it specified that "Name" is always the first line? For
* robustness I would prefer to have a real parser here. -wk */
if (strncmp(buffer, "Name:\t", 6)) return NULL;
end = strcspn(buffer + 6, "\n") + 6;
buffer[end] = 0;
/* check that uid matches what we expect */
uidstr = strstr(buffer + end + 1, "\nUid:\t");
if (!uidstr) return NULL;
if (atoi(uidstr + 6) != uid) return NULL;
return strdup(buffer + 6);
}
#endif /*!WINDOWS*/
const char *pinentry_get_pgmname(void) { return this_pgmname; }
/* Return a malloced string with the title. The caller mus free the
* string. If no title is available or the title string has an error
* NULL is returned. */
char *pinentry_get_title(pinentry_t pe) {
char *title;
if (pe->title) title = strdup(pe->title);
#ifndef WINDOWS
else if (pe->owner_pid) {
char buf[200];
struct utsname utsbuf;
char *pidname = NULL;
char *cmdline = NULL;
if (pe->owner_host && !uname(&utsbuf) &&
!strcmp(utsbuf.nodename, pe->owner_host)) {
pidname = get_pid_name_for_uid(pe->owner_pid, pe->owner_uid);
if (pidname) cmdline = get_cmdline(pe->owner_pid);
}
if (pe->owner_host && (cmdline || pidname))
snprintf(buf, sizeof buf, "[%lu]@%s (%s)", pe->owner_pid, pe->owner_host,
cmdline ? cmdline : pidname);
else if (pe->owner_host)
snprintf(buf, sizeof buf, "[%lu]@%s", pe->owner_pid, pe->owner_host);
else
snprintf(buf, sizeof buf, "[%lu] <unknown host>", pe->owner_pid);
free(pidname);
free(cmdline);
title = strdup(buf);
}
#endif /*!WINDOWS*/
else
title = strdup(this_pgmname);
return title;
}
/* Run a quality inquiry for PASSPHRASE of LENGTH. (We need LENGTH
because not all backends might be able to return a proper
C-string.). Returns: A value between -100 and 100 to give an
estimate of the passphrase's quality. Negative values are use if
the caller won't even accept that passphrase. Note that we expect
just one data line which should not be escaped in any represent a
numeric signed decimal value. Extra data is currently ignored but
should not be send at all. */
int pinentry_inq_quality(const QString &passphrase) {
int score = 0;
score += std::min(40, static_cast<int>(passphrase.length()) * 2);
bool has_upper = false;
bool has_lower = false;
bool has_digit = false;
bool has_special = false;
for (const auto ch : passphrase) {
if (ch.isUpper()) has_upper = true;
if (ch.isLower()) has_lower = true;
if (ch.isDigit()) has_digit = true;
if (!ch.isLetterOrNumber()) has_special = true;
}
int const variety_count =
static_cast<int>(has_upper) + static_cast<int>(has_lower) +
static_cast<int>(has_digit) + static_cast<int>(has_special);
score += variety_count * 10;
for (auto i = 0; i < passphrase.length() - 1; ++i) {
if (passphrase[i] == passphrase[i + 1]) {
score -= 5;
}
}
QHash<QChar, int> char_count;
for (const auto ch : passphrase) {
char_count[ch]++;
}
for (auto &p : char_count) {
if (p > 1) {
score -= (p - 1) * 3;
}
}
QString const lower_password = passphrase.toLower();
if (lower_password.contains("password") ||
lower_password.contains("123456")) {
score -= 30;
}
return std::max(-100, std::min(100, score));
}
/* Run a genpin inquiry */
char *pinentry_inq_genpin(pinentry_t pin) {
assuan_context_t ctx = (assuan_context_t)pin->ctx_assuan;
const char prefix[] = "INQUIRE GENPIN";
char *line;
size_t linelen;
int gotvalue = 0;
char *value = NULL;
int rc;
if (!ctx) return 0; /* Can't run the callback. */
rc = assuan_write_line(ctx, prefix);
if (rc) {
fprintf(stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc);
return 0;
}
for (;;) {
do {
rc = assuan_read_line(ctx, &line, &linelen);
if (rc) {
fprintf(stderr, "ASSUAN READ LINE failed: rc=%d\n", rc);
free(value);
return 0;
}
} while (*line == '#' || !linelen);
if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' &&
(!line[3] || line[3] == ' '))
break; /* END command received*/
if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' &&
(!line[3] || line[3] == ' '))
break; /* CAN command received*/
if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' &&
(!line[3] || line[3] == ' '))
break; /* ERR command received*/
if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) continue;
gotvalue = 1;
value = strdup(line + 2);
}
return value;
}
/* Try to make room for at least LEN bytes in the pinentry. Returns
new buffer on success and 0 on failure or when the old buffer is
sufficient. */
char *pinentry_setbufferlen(pinentry_t pin, int len) {
char *newp;
if (pin->pin_len)
assert(pin->pin);
else
assert(!pin->pin);
if (len < 2048) len = 2048;
if (len <= pin->pin_len) return pin->pin;
newp = SecureReallocAsType<char>(pin->pin, len);
if (newp) {
pin->pin = newp;
pin->pin_len = len;
} else {
GFFreeMemory(pin->pin);
pin->pin = 0;
pin->pin_len = 0;
}
return newp;
}
/* Set the optional flag used with getinfo. */
void pinentry_set_flavor_flag(const char *string) { flavor_flag = string; }

339
src/m_pinentry/pinentry.h Normal file
View File

@ -0,0 +1,339 @@
/* pinentry.h - The interface for the PIN entry support library.
* Copyright (C) 2002, 2003, 2010, 2015, 2021 g10 Code GmbH
*
* This file is part of PINENTRY.
*
* PINENTRY is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* PINENTRY is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef PINENTRY_H
#define PINENTRY_H
#include <cstdint>
#ifdef __cplusplus
extern "C" {
#if 0
}
#endif
#endif
typedef enum {
PINENTRY_COLOR_NONE,
PINENTRY_COLOR_DEFAULT,
PINENTRY_COLOR_BLACK,
PINENTRY_COLOR_RED,
PINENTRY_COLOR_GREEN,
PINENTRY_COLOR_YELLOW,
PINENTRY_COLOR_BLUE,
PINENTRY_COLOR_MAGENTA,
PINENTRY_COLOR_CYAN,
PINENTRY_COLOR_WHITE
} pinentry_color_t;
struct pinentry {
/* The window title, or NULL. (Assuan: "SETTITLE TITLE".) */
char *title;
/* The description to display, or NULL. (Assuan: "SETDESC
DESC".) */
char *description;
/* The error message to display, or NULL. (Assuan: "SETERROR
MESSAGE".) */
char *error;
/* The prompt to display, or NULL. (Assuan: "SETPROMPT
prompt".) */
char *prompt;
/* The OK button text to display, or NULL. (Assuan: "SETOK
OK".) */
char *ok;
/* The Not-OK button text to display, or NULL. This is the text for
the alternative option shown by the third button. (Assuan:
"SETNOTOK NOTOK".) */
char *notok;
/* The Cancel button text to display, or NULL. (Assuan: "SETCANCEL
CANCEL".) */
char *cancel;
/* The buffer to store the secret into. */
char *pin;
/* The length of the buffer. */
int pin_len;
/* Whether the pin was read from an external cache (1) or entered by
the user (0). */
int pin_from_cache;
/* The name of the X display to use if X is available and supported.
(Assuan: "OPTION display DISPLAY".) */
char *display;
/* The name of the terminal node to open if X not available or
supported. (Assuan: "OPTION ttyname TTYNAME".) */
char *ttyname;
/* The type of the terminal. (Assuan: "OPTION ttytype TTYTYPE".) */
char *ttytype_l;
/* Set the alert mode (none, beep or flash). */
char *ttyalert;
/* The LC_CTYPE value for the terminal. (Assuan: "OPTION lc-ctype
LC_CTYPE".) */
char *lc_ctype;
/* The LC_MESSAGES value for the terminal. (Assuan: "OPTION
lc-messages LC_MESSAGES".) */
char *lc_messages;
/* True if debug mode is requested. */
int debug;
/* The number of seconds before giving up while waiting for user input. */
int timeout;
/* True if caller should grab the keyboard. (Assuan: "OPTION grab"
or "OPTION no-grab".) */
int grab;
/* The PID of the owner or 0 if not known. The owner is the process
* which actually triggered the the pinentry. For example gpg. */
unsigned long owner_pid;
/* The numeric uid (user ID) of the owner process or -1 if not
* known. */
int owner_uid;
/* The malloced hostname of the owner or NULL. */
char *owner_host;
/* The window ID of the parent window over which the pinentry window
should be displayed. (Assuan: "OPTION parent-wid WID".) */
int parent_wid;
/* The name of an optional file which will be touched after a curses
entry has been displayed. (Assuan: "OPTION touch-file
FILENAME".) */
char *touch_file;
/* The frontend should set this to -1 if the user canceled the
request, and to the length of the PIN stored in pin
otherwise. */
int result;
/* The frontend should set this if the NOTOK button was pressed. */
int canceled;
/* The frontend should set this to true if an error with the local
conversion occurred. */
int locale_err;
/* The frontend should set this to a gpg-error so that commands are
able to return specific error codes. This is an ugly hack due to
the fact that pinentry_cmd_handler_t returns the length of the
passphrase or a negative error code. */
int specific_err;
/* The frontend may store a string with the error location here. */
const char *specific_err_loc;
/* The frontend may store a malloced string here to emit an ERROR
* status code with this extra info along with SPECIFIC_ERR. */
char *specific_err_info;
/* The frontend should set this to true if the window close button
has been used. This flag is used in addition to a regular return
value. */
int close_button;
/* The caller should set this to true if only one button is
required. This is useful for notification dialogs where only a
dismiss button is required. */
int one_button;
/* Whether this is a CONFIRM pinentry. */
int confirm;
/* If true a second prompt for the passphrase is shown and the user
is expected to enter the same passphrase again. Pinentry checks
that both match. (Assuan: "SETREPEAT".) */
char *repeat_passphrase;
/* The string to show if a repeated passphrase does not match.
(Assuan: "SETREPEATERROR ERROR".) */
char *repeat_error_string;
/* The string to show if a repeated passphrase does match.
(Assuan: "SETREPEATOK STRING".) */
char *repeat_ok_string;
/* Set to true if the passphrase has been entered a second time and
matches the first passphrase. */
int repeat_okay;
/* If this is not NULL, a passphrase quality indicator is shown.
There will also be an inquiry back to the caller to get an
indication of the quality for the passphrase entered so far. The
string is used as a label for the quality bar. (Assuan:
"SETQUALITYBAR LABEL".) */
char *quality_bar;
/* The tooltip to be shown for the qualitybar. Malloced or NULL.
(Assuan: "SETQUALITYBAR_TT TOOLTIP".) */
char *quality_bar_tt;
/* If this is not NULL, a generate action should be shown.
There will be an inquiry back to the caller to get such a
PIN. generate action. Malloced or NULL.
(Assuan: "SETGENPIN LABEL" .) */
char *genpin_label;
/* The tooltip to be shown for the generate action. Malloced or NULL.
(Assuan: "SETGENPIN_TT TOOLTIP".) */
char *genpin_tt;
/* Specifies whether passphrase formatting should be enabled.
(Assuan: "OPTION formatted-passphrase") */
int formatted_passphrase;
/* A hint to be shown near the passphrase input field if passphrase
formatting is enabled. Malloced or NULL.
(Assuan: "OPTION formatted-passphrase-hint=HINT".) */
char *formatted_passphrase_hint;
/* For the curses pinentry, the color of error messages. */
pinentry_color_t color_fg;
int color_fg_bright;
pinentry_color_t color_bg;
pinentry_color_t color_so;
int color_so_bright;
pinentry_color_t color_ok;
int color_ok_bright;
pinentry_color_t color_qualitybar;
int color_qualitybar_bright;
/* Malloced and i18ned default strings or NULL. These strings may
include an underscore character to indicate an accelerator key.
A double underscore represents a plain one. */
/* (Assuan: "OPTION default-ok OK"). */
char *default_ok;
/* (Assuan: "OPTION default-cancel CANCEL"). */
char *default_cancel;
/* (Assuan: "OPTION default-prompt PROMPT"). */
char *default_prompt;
/* (Assuan: "OPTION default-pwmngr
SAVE_PASSWORD_WITH_PASSWORD_MANAGER?"). */
char *default_pwmngr;
/* (Assuan: "OPTION default-cf-visi
Do you really want to make your passphrase visible?"). */
char *default_cf_visi;
/* (Assuan: "OPTION default-tt-visi
Make passphrase visible?"). */
char *default_tt_visi;
/* (Assuan: "OPTION default-tt-hide
Hide passphrase"). */
char *default_tt_hide;
/* (Assuan: "OPTION default-capshint
Caps Lock is on"). */
char *default_capshint;
/* Whether we are allowed to read the password from an external
cache. (Assuan: "OPTION allow-external-password-cache") */
int allow_external_password_cache;
/* We only try the cache once. */
int tried_password_cache;
/* A stable identifier for the key. (Assuan: "SETKEYINFO
KEYINFO".) */
char *keyinfo;
/* Whether we may cache the password (according to the user). */
int may_cache_password;
/* NOTE: If you add any additional fields to this structure, be sure
to update the initializer in pinentry/pinentry.c!!! */
/* For the quality indicator and genpin we need to do an inquiry.
Thus we need to save the assuan ctx. */
void *ctx_assuan;
/* An UTF-8 string with an invisible character used to override the
default in some pinentries. Only the first character is
used. */
char *invisible_char;
/* Whether the passphrase constraints are enforced by gpg-agent.
(Assuan: "OPTION constraints-enforce") */
int constraints_enforce;
/* A short translated hint for the user with the constraints for new
passphrases to be displayed near the passphrase input field.
Malloced or NULL.
(Assuan: "OPTION constraints-hint-short=At least 8 characters".) */
char *constraints_hint_short;
/* A longer translated hint for the user with the constraints for new
passphrases to be displayed for example as tooltip. Malloced or NULL.
(Assuan: "OPTION constraints-hint-long=The passphrase must ...".) */
char *constraints_hint_long;
/* A short translated title for an error dialog informing the user about
unsatisfied passphrase constraints. Malloced or NULL.
(Assuan: "OPTION constraints-error-title=Passphrase Not Allowed".) */
char *constraints_error_title;
};
typedef struct pinentry *pinentry_t;
/* The pinentry command handler type processes the pinentry request
PIN. If PIN->pin is zero, request a confirmation, otherwise a PIN
entry. On confirmation, the function should return TRUE if
confirmed, and FALSE otherwise. On PIN entry, the function should
return -1 if an error occurred or the user cancelled the operation
and 1 otherwise. */
typedef int (*pinentry_cmd_handler_t)(pinentry_t pin);
const char *pinentry_get_pgmname(void);
char *pinentry_get_title(pinentry_t pe);
/* Run a quality inquiry for PASSPHRASE of LENGTH. */
int pinentry_inq_quality(const QString &passphrase);
/* Run a genpin iquriry. Returns a malloced string or NULL */
char *pinentry_inq_genpin(pinentry_t pin);
/* Try to make room for at least LEN bytes for the pin in the pinentry
PIN. Returns new buffer on success and 0 on failure. */
char *pinentry_setbufferlen(pinentry_t pin, int len);
/* Return true if either DISPLAY is set or ARGV contains the string
"--display". */
int pinentry_have_display(int argc, char **argv);
/* Parse the command line options. May exit the program if only help
or version output is requested. */
void pinentry_parse_opts(int argc, char *argv[]);
/* Set the optional flag used with getinfo. */
void pinentry_set_flavor_flag(const char *string);
#ifdef WINDOWS
/* Windows declares sleep as obsolete, but provides a definition for
_sleep but non for the still existing sleep. */
#define sleep(a) _sleep((a))
#endif /*WINDOWS*/
#if 0
{
#endif
#ifdef __cplusplus
}
#endif
#endif /* PINENTRY_H */

View File

@ -0,0 +1,10 @@
<!DOCTYPE RCC>
<RCC>
<qresource prefix="/icons">
<file alias="data-error.svg">icons/data-error.svg</file>
<file alias="document-encrypt.png">icons/document-encrypt.png</file>
<file alias="hint.svg">icons/hint.svg</file>
<file alias="password-generate.svg">icons/password-generate.svg</file>
<file alias="visibility.svg">icons/visibility.svg</file>
</qresource>
</RCC>

View File

@ -0,0 +1,31 @@
/* pinentry_debug.h - Logging category for pinentry
* Copyright (C) 2021 g10 Code GmbH
*
* Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "pinentry_debug.h"
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
Q_LOGGING_CATEGORY(PINENTRY_LOG, "gpg.pinentry", QtWarningMsg)
#else
Q_LOGGING_CATEGORY(PINENTRY_LOG, "gpg.pinentry")
#endif

View File

@ -0,0 +1,28 @@
/* pinentry_debug.h - Logging category for pinentry
* Copyright (C) 2021 g10 Code GmbH
*
* Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef __PINENTRY_QT_DEBUG_H__
#define __PINENTRY_QT_DEBUG_H__
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(PINENTRY_LOG)
#endif // __PINENTRY_QT_DEBUG_H__

View File

@ -0,0 +1,123 @@
/* pinentryconfirm.cpp - A QMessageBox with a timeout
*
* Copyright (C) 2011 Ben Kibbey <bjk@luxsci.net>
* Copyright (C) 2022 g10 Code GmbH
*
* Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#include "pinentryconfirm.h"
#include <QAbstractButton>
#include <QApplication>
#include <QFontMetrics>
#include <QGridLayout>
#include <QLabel>
#include <QSpacerItem>
#include "accessibility.h"
#include "pinentrydialog.h"
namespace {
QLabel *messageBoxLabel(QMessageBox *messageBox) {
return messageBox->findChild<QLabel *>(QStringLiteral("qt_msgbox_label"));
}
} // namespace
PinentryConfirm::PinentryConfirm(Icon icon, const QString &title,
const QString &text, StandardButtons buttons,
QWidget *parent, Qt::WindowFlags flags)
: QMessageBox{icon, title, text, buttons, parent, flags} {
_timer.callOnTimeout(this, &PinentryConfirm::slotTimeout);
#ifndef QT_NO_ACCESSIBILITY
QAccessible::installActivationObserver(this);
accessibilityActiveChanged(QAccessible::isActive());
#endif
#if QT_VERSION >= 0x050000
/* This is in line with PinentryDialog ctor to have a maximizing
* animation when opening. */
if (qApp->platformName() != QLatin1String("wayland")) {
setWindowState(Qt::WindowMinimized);
QTimer::singleShot(0, this, [this]() { raiseWindow(this); });
}
#else
activateWindow();
raise();
#endif
}
PinentryConfirm::~PinentryConfirm() {
#ifndef QT_NO_ACCESSIBILITY
QAccessible::removeActivationObserver(this);
#endif
}
void PinentryConfirm::setTimeout(std::chrono::seconds timeout) {
_timer.setInterval(timeout);
}
std::chrono::seconds PinentryConfirm::timeout() const {
return std::chrono::duration_cast<std::chrono::seconds>(
_timer.intervalAsDuration());
}
bool PinentryConfirm::timedOut() const { return _timed_out; }
void PinentryConfirm::showEvent(QShowEvent *event) {
static bool resized;
if (!resized) {
QGridLayout *lay = dynamic_cast<QGridLayout *>(layout());
if (lay) {
QSize textSize = fontMetrics().size(Qt::TextExpandTabs, text(),
fontMetrics().maxWidth());
QSpacerItem *horizontalSpacer =
new QSpacerItem(textSize.width() + iconPixmap().width(), 0,
QSizePolicy::Minimum, QSizePolicy::Expanding);
lay->addItem(horizontalSpacer, lay->rowCount(), 1, 1,
lay->columnCount() - 1);
}
resized = true;
}
QMessageBox::showEvent(event);
if (timeout() > std::chrono::milliseconds::zero()) {
_timer.setSingleShot(true);
_timer.start();
}
}
void PinentryConfirm::slotTimeout() {
QAbstractButton *b = button(QMessageBox::Cancel);
_timed_out = true;
if (b) {
b->animateClick();
}
}
#ifndef QT_NO_ACCESSIBILITY
void PinentryConfirm::accessibilityActiveChanged(bool active) {
// Allow text label to get focus if accessibility is active
const auto focusPolicy = active ? Qt::StrongFocus : Qt::ClickFocus;
if (auto label = messageBoxLabel(this)) {
label->setFocusPolicy(focusPolicy);
}
}
#endif

View File

@ -0,0 +1,63 @@
/* pinentryconfirm.h - A QMessageBox with a timeout
*
* Copyright (C) 2011 Ben Kibbey <bjk@luxsci.net>
* Copyright (C) 2022 g10 Code GmbH
*
* Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef PINENTRYCONFIRM_H
#define PINENTRYCONFIRM_H
#include <QAccessible>
#include <QMessageBox>
#include <QTimer>
class PinentryConfirm : public QMessageBox
#ifndef QT_NO_ACCESSIBILITY
, public QAccessible::ActivationObserver
#endif
{
Q_OBJECT
public:
PinentryConfirm(Icon icon, const QString &title, const QString &text,
StandardButtons buttons = NoButton, QWidget *parent = nullptr,
Qt::WindowFlags flags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint);
~PinentryConfirm() override;
void setTimeout(std::chrono::seconds timeout);
std::chrono::seconds timeout() const;
bool timedOut() const;
protected:
void showEvent(QShowEvent *event) override;
private Q_SLOTS:
void slotTimeout();
private:
#ifndef QT_NO_ACCESSIBILITY
void accessibilityActiveChanged(bool active) override;
#endif
private:
QTimer _timer;
bool _timed_out = false;
};
#endif

View File

@ -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 <steffen@klaralvdalens-datakonsult.se>.
* Modified by Andre Heinecke <aheinecke@intevation.de>
* Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#include "pinentrydialog.h"
#include <qnamespace.h>
#include <QAccessible>
#include <QAction>
#include <QApplication>
#include <QCheckBox>
#include <QDebug>
#include <QDialogButtonBox>
#include <QFontMetrics>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPainter>
#include <QPalette>
#include <QProgressBar>
#include <QPushButton>
#include <QRegularExpression>
#include <QStyle>
#include <QVBoxLayout>
#include "GFModuleCommonUtils.hpp"
#include "accessibility.h"
#include "capslock/capslock.h"
#include "pinentry.h"
#include "pinlineedit.h"
void raiseWindow(QWidget *w) {
w->setWindowState((w->windowState() & ~Qt::WindowMinimized) |
Qt::WindowActive);
w->activateWindow();
w->raise();
}
auto applicationIconPixmap(const QIcon &overlayIcon) -> QPixmap {
QPixmap pm = qApp->windowIcon().pixmap(48, 48);
if (!overlayIcon.isNull()) {
QPainter painter(&pm);
const int emblem_size = 22;
painter.drawPixmap(pm.width() - emblem_size, 0,
overlayIcon.pixmap(emblem_size, emblem_size));
}
return pm;
}
void PinEntryDialog::slotTimeout() {
_timed_out = true;
reject();
}
PinEntryDialog::PinEntryDialog(QWidget *parent, const char *name, int timeout,
bool modal, bool enable_quality_bar,
const QString &repeatString,
const QString &visibilityTT,
const QString &hideTT)
: QDialog{parent},
_have_quality_bar{enable_quality_bar},
mVisibilityTT{visibilityTT},
mHideTT{hideTT} {
Q_UNUSED(name)
if (modal) {
setWindowModality(Qt::ApplicationModal);
setModal(true);
}
QPalette red_text_palette;
red_text_palette.setColor(QPalette::WindowText, Qt::red);
auto *const main_layout = new QVBoxLayout{this};
auto *const hbox = new QHBoxLayout;
_icon = new QLabel(this);
_icon->setPixmap(applicationIconPixmap());
hbox->addWidget(_icon, 0, Qt::AlignVCenter | Qt::AlignLeft);
auto *const grid = new QGridLayout;
int row = 1;
_error = new QLabel{this};
_error->setTextFormat(Qt::PlainText);
_error->setTextInteractionFlags(Qt::TextSelectableByMouse);
_error->setPalette(red_text_palette);
_error->hide();
grid->addWidget(_error, row, 1, 1, 2);
row++;
_desc = new QLabel{this};
_desc->setTextFormat(Qt::PlainText);
_desc->setTextInteractionFlags(Qt::TextSelectableByMouse);
_desc->hide();
grid->addWidget(_desc, row, 1, 1, 2);
row++;
mCapsLockHint = new QLabel{this};
mCapsLockHint->setTextFormat(Qt::PlainText);
mCapsLockHint->setTextInteractionFlags(Qt::TextSelectableByMouse);
mCapsLockHint->setPalette(red_text_palette);
mCapsLockHint->setAlignment(Qt::AlignCenter);
mCapsLockHint->setVisible(false);
grid->addWidget(mCapsLockHint, row, 1, 1, 2);
row++;
{
_prompt = new QLabel(this);
_prompt->setTextFormat(Qt::PlainText);
_prompt->setTextInteractionFlags(Qt::TextSelectableByMouse);
_prompt->hide();
grid->addWidget(_prompt, row, 1);
auto *const l = new QHBoxLayout;
_edit = new PinLineEdit(this);
_edit->setMaxLength(256);
_edit->setMinimumWidth(_edit->fontMetrics().averageCharWidth() * 20 + 48);
_edit->setEchoMode(QLineEdit::Password);
_prompt->setBuddy(_edit);
l->addWidget(_edit, 1);
if (!repeatString.isNull()) {
mGenerateButton = new QPushButton{this};
mGenerateButton->setIcon(
QIcon(QLatin1String(":/icons/password-generate.svg")));
mGenerateButton->setVisible(false);
l->addWidget(mGenerateButton);
}
grid->addLayout(l, row, 2);
}
/* Set up the show password action */
const QIcon visibility_icon = QIcon(QLatin1String(":/icons/visibility.svg"));
const QIcon hide_icon = QIcon(QLatin1String(":/icons/hint.svg"));
#if QT_VERSION >= 0x050200
if (!visibility_icon.isNull() && !hide_icon.isNull()) {
mVisiActionEdit =
_edit->addAction(visibility_icon, QLineEdit::TrailingPosition);
mVisiActionEdit->setVisible(false);
mVisiActionEdit->setToolTip(mVisibilityTT);
} else
#endif
{
if (!mVisibilityTT.isNull()) {
row++;
mVisiCB = new QCheckBox{mVisibilityTT, this};
grid->addWidget(mVisiCB, row, 1, 1, 2, Qt::AlignLeft);
}
}
row++;
mConstraintsHint = new QLabel{this};
mConstraintsHint->setTextFormat(Qt::PlainText);
mConstraintsHint->setTextInteractionFlags(Qt::TextSelectableByMouse);
mConstraintsHint->setVisible(false);
grid->addWidget(mConstraintsHint, row, 2);
row++;
mFormattedPassphraseHintSpacer = new QLabel{this};
mFormattedPassphraseHintSpacer->setVisible(false);
mFormattedPassphraseHint = new QLabel{this};
mFormattedPassphraseHint->setTextFormat(Qt::PlainText);
mFormattedPassphraseHint->setTextInteractionFlags(Qt::TextSelectableByMouse);
mFormattedPassphraseHint->setVisible(false);
grid->addWidget(mFormattedPassphraseHintSpacer, row, 1);
grid->addWidget(mFormattedPassphraseHint, row, 2);
if (!repeatString.isNull()) {
row++;
auto *repeat_label = new QLabel{this};
repeat_label->setTextFormat(Qt::PlainText);
repeat_label->setTextInteractionFlags(Qt::TextSelectableByMouse);
repeat_label->setText(repeatString);
grid->addWidget(repeat_label, row, 1);
mRepeat = new PinLineEdit(this);
mRepeat->setMaxLength(256);
mRepeat->setEchoMode(QLineEdit::Password);
repeat_label->setBuddy(mRepeat);
grid->addWidget(mRepeat, row, 2);
row++;
mRepeatError = new QLabel{this};
mRepeatError->setTextFormat(Qt::PlainText);
mRepeatError->setTextInteractionFlags(Qt::TextSelectableByMouse);
mRepeatError->setPalette(red_text_palette);
mRepeatError->hide();
grid->addWidget(mRepeatError, row, 2);
}
if (enable_quality_bar) {
row++;
_quality_bar_label = new QLabel(this);
_quality_bar_label->setTextFormat(Qt::PlainText);
_quality_bar_label->setTextInteractionFlags(Qt::TextSelectableByMouse);
_quality_bar_label->setAlignment(Qt::AlignVCenter);
grid->addWidget(_quality_bar_label, row, 1);
_quality_bar = new QProgressBar(this);
_quality_bar->setAlignment(Qt::AlignCenter);
_quality_bar_label->setBuddy(_quality_bar);
grid->addWidget(_quality_bar, row, 2);
}
hbox->addLayout(grid, 1);
main_layout->addLayout(hbox);
auto *const buttons = new QDialogButtonBox(this);
buttons->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
_ok = buttons->button(QDialogButtonBox::Ok);
_cancel = buttons->button(QDialogButtonBox::Cancel);
if (style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons)) {
_ok->setIcon(style()->standardIcon(QStyle::SP_DialogOkButton));
_cancel->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton));
}
main_layout->addStretch(1);
main_layout->addWidget(buttons);
main_layout->setSizeConstraint(QLayout::SetFixedSize);
if (timeout > 0) {
_timer = new QTimer(this);
connect(_timer, &QTimer::timeout, this, &PinEntryDialog::slotTimeout);
_timer->start(timeout * 1000);
}
connect(buttons, &QDialogButtonBox::accepted, this,
&PinEntryDialog::onAccept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(_edit, &QLineEdit::textChanged, this, &PinEntryDialog::updateQuality);
connect(_edit, &QLineEdit::textChanged, this, &PinEntryDialog::textChanged);
connect(_edit, &PinLineEdit::backspacePressed, this,
&PinEntryDialog::onBackspace);
if (mGenerateButton != nullptr) {
connect(mGenerateButton, &QPushButton::clicked, this,
&PinEntryDialog::generatePin);
}
if (mVisiActionEdit != nullptr) {
connect(mVisiActionEdit, &QAction::triggered, this,
&PinEntryDialog::toggleVisibility);
}
if (mVisiCB != nullptr) {
connect(mVisiCB, &QCheckBox::toggled, this,
&PinEntryDialog::toggleVisibility);
}
if (mRepeat != nullptr) {
connect(mRepeat, &QLineEdit::textChanged, this,
&PinEntryDialog::textChanged);
}
auto *caps_lock_watcher = new CapsLockWatcher{this};
connect(caps_lock_watcher, &CapsLockWatcher::stateChanged, this,
[this](bool locked) { mCapsLockHint->setVisible(locked); });
connect(qApp, &QApplication::focusChanged, this,
&PinEntryDialog::focusChanged);
connect(qApp, &QApplication::applicationStateChanged, this,
&PinEntryDialog::checkCapsLock);
checkCapsLock();
setAttribute(Qt::WA_DeleteOnClose);
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
/* This is mostly an issue on Windows where this results
in the pinentry popping up nicely with an animation and
comes to front. It is not ifdefed for Windows only since
window managers on Linux like KWin can also have this
result in an animation when the pinentry is shown and
not just popping it up.
*/
if (qApp->platformName() != QLatin1String("wayland")) {
setWindowState(Qt::WindowMinimized);
QTimer::singleShot(0, this, [this]() { raiseWindow(this); });
} else {
raiseWindow(this);
}
}
void PinEntryDialog::keyPressEvent(QKeyEvent *e) {
const auto return_pressed =
(!e->modifiers() &&
(e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return)) ||
(e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter);
if (return_pressed && _edit->hasFocus() && (mRepeat != nullptr)) {
// if the user pressed Return in the first input field, then move the
// focus to the repeat input field and prevent further event processing
// by QDialog (which would trigger the default button)
mRepeat->setFocus();
e->ignore();
return;
}
QDialog::keyPressEvent(e);
}
void PinEntryDialog::keyReleaseEvent(QKeyEvent *event) {
QDialog::keyReleaseEvent(event);
checkCapsLock();
}
void PinEntryDialog::showEvent(QShowEvent *event) {
QDialog::showEvent(event);
_edit->setFocus();
}
void PinEntryDialog::setDescription(const QString &txt) {
_desc->setVisible(!txt.isEmpty());
_desc->setText(txt);
_icon->setPixmap(applicationIconPixmap());
setError(QString());
}
QString PinEntryDialog::description() const { return _desc->text(); }
void PinEntryDialog::setError(const QString &txt) {
if (!txt.isNull()) {
_icon->setPixmap(
applicationIconPixmap(QIcon{QStringLiteral(":/icons/data-error.svg")}));
}
_error->setText(txt);
_error->setVisible(!txt.isEmpty());
}
QString PinEntryDialog::error() const { return _error->text(); }
void PinEntryDialog::setPin(const QString &txt) { _edit->setPin(txt); }
QString PinEntryDialog::pin() const { return _edit->pin(); }
void PinEntryDialog::setPrompt(const QString &txt) {
_prompt->setText(txt);
_prompt->setVisible(!txt.isEmpty());
if (txt.contains("PIN")) _disable_echo_allowed = false;
}
QString PinEntryDialog::prompt() const { return _prompt->text(); }
void PinEntryDialog::setOkText(const QString &txt) {
_ok->setText(txt);
_ok->setVisible(!txt.isEmpty());
}
void PinEntryDialog::setCancelText(const QString &txt) {
_cancel->setText(txt);
_cancel->setVisible(!txt.isEmpty());
}
void PinEntryDialog::setQualityBar(const QString &txt) {
if (_have_quality_bar) {
_quality_bar_label->setText(txt);
}
}
void PinEntryDialog::setQualityBarTT(const QString &txt) {
if (_have_quality_bar) {
_quality_bar->setToolTip(txt);
}
}
void PinEntryDialog::setGenpinLabel(const QString &txt) {
if (mGenerateButton == nullptr) {
return;
}
mGenerateButton->setVisible(!txt.isEmpty());
if (!txt.isEmpty()) {
Accessibility::setName(mGenerateButton, txt);
}
}
void PinEntryDialog::setGenpinTT(const QString &txt) {
if (mGenerateButton != nullptr) {
mGenerateButton->setToolTip(txt);
}
}
void PinEntryDialog::setCapsLockHint(const QString &txt) {
mCapsLockHint->setText(txt);
}
void PinEntryDialog::setFormattedPassphrase(
const PinEntryDialog::FormattedPassphraseOptions &options) {
mFormatPassphrase = options.formatPassphrase;
mFormattedPassphraseHint->setTextFormat(Qt::RichText);
mFormattedPassphraseHint->setText(QLatin1String("<html>") +
options.hint.toHtmlEscaped() +
QLatin1String("</html>"));
Accessibility::setName(mFormattedPassphraseHint, options.hint);
// toggleFormattedPassphrase();
}
void PinEntryDialog::setConstraintsOptions(const ConstraintsOptions &options) {
mEnforceConstraints = options.enforce;
mConstraintsHint->setText(options.shortHint);
if (!options.longHint.isEmpty()) {
mConstraintsHint->setToolTip(
QLatin1String("<html>") +
options.longHint.toHtmlEscaped().replace(QLatin1String("\n\n"),
QLatin1String("<br>")) +
QLatin1String("</html>"));
Accessibility::setDescription(mConstraintsHint, options.longHint);
}
mConstraintsErrorTitle = options.errorTitle;
mConstraintsHint->setVisible(mEnforceConstraints &&
!options.shortHint.isEmpty());
}
void PinEntryDialog::toggleFormattedPassphrase() {
const bool enable_formatting =
mFormatPassphrase && _edit->echoMode() == QLineEdit::Normal;
_edit->setFormattedPassphrase(enable_formatting);
if (mRepeat != nullptr) {
mRepeat->setFormattedPassphrase(enable_formatting);
const bool hint_about_to_be_hidden =
mFormattedPassphraseHint->isVisible() && !enable_formatting;
if (hint_about_to_be_hidden) {
// set hint spacer to current height of hint label before hiding the hint
mFormattedPassphraseHintSpacer->setMinimumHeight(
mFormattedPassphraseHint->height());
mFormattedPassphraseHintSpacer->setVisible(true);
} else if (enable_formatting) {
mFormattedPassphraseHintSpacer->setVisible(false);
}
mFormattedPassphraseHint->setVisible(enable_formatting);
}
}
void PinEntryDialog::onBackspace() {
cancelTimeout();
if (_disable_echo_allowed) {
_edit->setEchoMode(QLineEdit::NoEcho);
if (mRepeat != nullptr) {
mRepeat->setEchoMode(QLineEdit::NoEcho);
}
}
}
void PinEntryDialog::updateQuality(const QString &txt) {
int length;
int percent;
QPalette pal;
_disable_echo_allowed = false;
if (!_have_quality_bar) {
return;
}
length = txt.length();
percent = length != 0 ? pinentry_inq_quality(txt) : 0;
if (length == 0) {
_quality_bar->reset();
} else {
pal = _quality_bar->palette();
if (percent < 0) {
pal.setColor(QPalette::Highlight, QColor("red"));
percent = -percent;
} else {
pal.setColor(QPalette::Highlight, QColor("green"));
}
_quality_bar->setPalette(pal);
_quality_bar->setValue(percent);
}
}
void PinEntryDialog::setPinentryInfo(struct pinentry peinfo) {
_pinentry_info = SecureCreateQSharedObject<struct pinentry>(peinfo);
}
void PinEntryDialog::focusChanged(QWidget *old, QWidget *now) {
// Grab keyboard. It might be a little weird to do it here, but it works!
// Previously this code was in showEvent, but that did not work in Qt4.
if (!_pinentry_info || (_pinentry_info->grab != 0)) {
if (_grabbed && (old != nullptr) && (old == _edit || old == mRepeat)) {
old->releaseKeyboard();
_grabbed = false;
}
if (!_grabbed && (now != nullptr) && (now == _edit || now == mRepeat)) {
now->grabKeyboard();
_grabbed = true;
}
}
}
void PinEntryDialog::textChanged(const QString &text) {
Q_UNUSED(text);
cancelTimeout();
if ((mVisiActionEdit != nullptr) && sender() == _edit) {
mVisiActionEdit->setVisible(!_edit->pin().isEmpty());
}
if (mGenerateButton != nullptr) {
mGenerateButton->setVisible(_edit->pin().isEmpty()
#ifndef QT_NO_ACCESSIBILITY
&& !mGenerateButton->accessibleName().isEmpty()
#endif
);
}
}
void PinEntryDialog::generatePin() {
// std::unique_ptr<char> pin{pinentry_inq_genpin(_pinentry_info.get())};
// if (pin) {
// if (_edit->echoMode() == QLineEdit::Password) {
// if (mVisiActionEdit != nullptr) {
// mVisiActionEdit->trigger();
// }
// if (mVisiCB != nullptr) {
// mVisiCB->setChecked(true);
// }
// }
// const auto pin_str = QString::fromUtf8(pin.get());
// _edit->setPin(pin_str);
// mRepeat->setPin(pin_str);
// // explicitly focus the first input field and select the generated
// password _edit->setFocus(); _edit->selectAll();
// }
}
void PinEntryDialog::toggleVisibility() {
if (sender() != mVisiCB) {
if (_edit->echoMode() == QLineEdit::Password) {
if (mVisiActionEdit != nullptr) {
mVisiActionEdit->setIcon(QIcon(QLatin1String(":/icons/hint.svg")));
mVisiActionEdit->setToolTip(mHideTT);
}
_edit->setEchoMode(QLineEdit::Normal);
if (mRepeat != nullptr) {
mRepeat->setEchoMode(QLineEdit::Normal);
}
} else {
if (mVisiActionEdit != nullptr) {
mVisiActionEdit->setIcon(
QIcon(QLatin1String(":/icons/visibility.svg")));
mVisiActionEdit->setToolTip(mVisibilityTT);
}
_edit->setEchoMode(QLineEdit::Password);
if (mRepeat != nullptr) {
mRepeat->setEchoMode(QLineEdit::Password);
}
}
} else {
if (mVisiCB->isChecked()) {
if (mRepeat != nullptr) {
mRepeat->setEchoMode(QLineEdit::Normal);
}
_edit->setEchoMode(QLineEdit::Normal);
} else {
if (mRepeat != nullptr) {
mRepeat->setEchoMode(QLineEdit::Password);
}
_edit->setEchoMode(QLineEdit::Password);
}
}
toggleFormattedPassphrase();
}
QString PinEntryDialog::repeatedPin() const {
if (mRepeat != nullptr) {
return mRepeat->pin();
}
return QString();
}
bool PinEntryDialog::timedOut() const { return _timed_out; }
void PinEntryDialog::setRepeatErrorText(const QString &err) {
if (mRepeatError != nullptr) {
mRepeatError->setText(err);
}
}
void PinEntryDialog::cancelTimeout() {
if (_timer != nullptr) {
_timer->stop();
}
}
void PinEntryDialog::checkCapsLock() {
const auto state = capsLockState();
if (state != LockState::Unknown) {
mCapsLockHint->setVisible(state == LockState::On);
}
}
void PinEntryDialog::onAccept() {
cancelTimeout();
if ((mRepeat != nullptr) && mRepeat->pin() != _edit->pin()) {
#ifndef QT_NO_ACCESSIBILITY
if (QAccessible::isActive()) {
QMessageBox::information(this, mRepeatError->text(),
mRepeatError->text());
} else
#endif
{
mRepeatError->setVisible(true);
}
return;
}
accept();
}

View File

@ -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 <steffen@klaralvdalens-datakonsult.se>.
* Modified by Andre Heinecke <aheinecke@intevation.de>
* Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef __PINENTRYDIALOG_H__
#define __PINENTRYDIALOG_H__
#include <QAccessible>
#include <QDialog>
#include <QStyle>
#include <QTimer>
#include "pinentry.h"
class QIcon;
class QLabel;
class QPushButton;
class QLineEdit;
class PinLineEdit;
class QString;
class QProgressBar;
class QCheckBox;
class QAction;
QPixmap applicationIconPixmap(const QIcon &overlayIcon = {});
void raiseWindow(QWidget *w);
class PinEntryDialog : public QDialog {
Q_OBJECT
Q_PROPERTY(QString description READ description WRITE setDescription)
Q_PROPERTY(QString error READ error WRITE setError)
Q_PROPERTY(QString pin READ pin WRITE setPin)
Q_PROPERTY(QString prompt READ prompt WRITE setPrompt)
public:
struct FormattedPassphraseOptions {
bool formatPassphrase;
QString hint;
};
struct ConstraintsOptions {
bool enforce;
QString shortHint;
QString longHint;
QString errorTitle;
};
explicit PinEntryDialog(QWidget *parent = 0, const char *name = 0,
int timeout = 0, bool modal = false,
bool enable_quality_bar = false,
const QString &repeatString = QString(),
const QString &visibiltyTT = QString(),
const QString &hideTT = QString());
void setDescription(const QString &);
QString description() const;
void setError(const QString &);
QString error() const;
void setPin(const QString &);
QString pin() const;
QString repeatedPin() const;
void setRepeatErrorText(const QString &);
void setPrompt(const QString &);
QString prompt() const;
void setOkText(const QString &);
void setCancelText(const QString &);
void setQualityBar(const QString &);
void setQualityBarTT(const QString &);
void setGenpinLabel(const QString &);
void setGenpinTT(const QString &);
void setCapsLockHint(const QString &);
void setFormattedPassphrase(const FormattedPassphraseOptions &options);
void setConstraintsOptions(const ConstraintsOptions &options);
void setPinentryInfo(struct pinentry);
bool timedOut() const;
protected Q_SLOTS:
void updateQuality(const QString &);
void slotTimeout();
void textChanged(const QString &);
void focusChanged(QWidget *old, QWidget *now);
void toggleVisibility();
void onBackspace();
void generatePin();
void toggleFormattedPassphrase();
protected:
void keyPressEvent(QKeyEvent *event) override;
void keyReleaseEvent(QKeyEvent *event) override;
void showEvent(QShowEvent *event) override;
private Q_SLOTS:
void cancelTimeout();
void checkCapsLock();
void onAccept();
private:
enum PassphraseCheckResult {
PassphraseNotChecked = -1,
PassphraseNotOk = 0,
PassphraseOk
};
QLabel *_icon = nullptr;
QLabel *_desc = nullptr;
QLabel *_error = nullptr;
QLabel *_prompt = nullptr;
QLabel *_quality_bar_label = nullptr;
QProgressBar *_quality_bar = nullptr;
PinLineEdit *_edit = nullptr;
PinLineEdit *mRepeat = nullptr;
QLabel *mRepeatError = nullptr;
QPushButton *_ok = nullptr;
QPushButton *_cancel = nullptr;
bool _grabbed = false;
bool _have_quality_bar = false;
bool _timed_out = false;
bool _disable_echo_allowed = true;
bool mEnforceConstraints = false;
bool mFormatPassphrase = false;
QSharedPointer<struct pinentry> _pinentry_info = nullptr;
QTimer *_timer = nullptr;
QString mVisibilityTT;
QString mHideTT;
QAction *mVisiActionEdit = nullptr;
QPushButton *mGenerateButton = nullptr;
QCheckBox *mVisiCB = nullptr;
QLabel *mFormattedPassphraseHint = nullptr;
QLabel *mFormattedPassphraseHintSpacer = nullptr;
QLabel *mCapsLockHint = nullptr;
QLabel *mConstraintsHint = nullptr;
QString mConstraintsErrorTitle;
};
#endif // __PINENTRYDIALOG_H__

View File

@ -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 <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#include "pinlineedit.h"
#include <QClipboard>
#include <QGuiApplication>
#include <QKeyEvent>
static const int FormattedPassphraseGroupSize = 5;
static const QChar FormattedPassphraseSeparator = QChar::Nbsp;
namespace {
struct Selection {
bool empty() const { return start < 0 || start >= end; }
int length() const { return empty() ? 0 : end - start; }
int start;
int end;
};
} // namespace
class PinLineEdit::Private {
PinLineEdit *const q;
public:
Private(PinLineEdit *q) : q{q} {}
QString formatted(QString text) const {
const int dashCount = text.size() / FormattedPassphraseGroupSize;
text.reserve(text.size() + dashCount);
for (int i = FormattedPassphraseGroupSize; i < text.size();
i += FormattedPassphraseGroupSize + 1) {
text.insert(i, FormattedPassphraseSeparator);
}
return text;
}
Selection formattedSelection(Selection selection) const {
if (selection.empty()) {
return selection;
}
return {selection.start + selection.start / FormattedPassphraseGroupSize,
selection.end + (selection.end - 1) / FormattedPassphraseGroupSize};
}
QString unformatted(QString text) const {
for (int i = FormattedPassphraseGroupSize; i < text.size();
i += FormattedPassphraseGroupSize) {
text.remove(i, 1);
}
return text;
}
Selection unformattedSelection(Selection selection) const {
if (selection.empty()) {
return selection;
}
return {
selection.start - selection.start / (FormattedPassphraseGroupSize + 1),
selection.end - selection.end / (FormattedPassphraseGroupSize + 1)};
}
void copyToClipboard() {
if (q->echoMode() != QLineEdit::Normal) {
return;
}
QString text = q->selectedText();
if (mFormattedPassphrase) {
text.remove(FormattedPassphraseSeparator);
}
if (!text.isEmpty()) {
QGuiApplication::clipboard()->setText(text);
}
}
int selectionEnd() {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
return q->selectionEnd();
#else
return q->selectionStart() + q->selectedText().size();
#endif
}
public:
bool mFormattedPassphrase = false;
};
PinLineEdit::PinLineEdit(QWidget *parent)
: QLineEdit(parent), d{new Private{this}} {
connect(this, SIGNAL(textEdited(QString)), this, SLOT(textEdited()));
}
PinLineEdit::~PinLineEdit() = default;
void PinLineEdit::setFormattedPassphrase(bool on) {
if (on == d->mFormattedPassphrase) {
return;
}
d->mFormattedPassphrase = on;
Selection selection{selectionStart(), d->selectionEnd()};
if (d->mFormattedPassphrase) {
setText(d->formatted(text()));
selection = d->formattedSelection(selection);
} else {
setText(d->unformatted(text()));
selection = d->unformattedSelection(selection);
}
if (!selection.empty()) {
setSelection(selection.start, selection.length());
}
}
void PinLineEdit::copy() const { d->copyToClipboard(); }
void PinLineEdit::cut() {
if (hasSelectedText()) {
copy();
del();
}
}
void PinLineEdit::setPin(const QString &pin) {
setText(d->mFormattedPassphrase ? d->formatted(pin) : pin);
}
QString PinLineEdit::pin() const {
if (d->mFormattedPassphrase) {
return d->unformatted(text());
} else {
return text();
}
}
void PinLineEdit::keyPressEvent(QKeyEvent *e) {
if (e == QKeySequence::Copy) {
copy();
return;
} else if (e == QKeySequence::Cut) {
if (!isReadOnly() && hasSelectedText()) {
copy();
del();
}
return;
} else if (e == QKeySequence::DeleteEndOfLine) {
if (!isReadOnly()) {
setSelection(cursorPosition(), text().size());
copy();
del();
}
return;
} else if (e == QKeySequence::DeleteCompleteLine) {
if (!isReadOnly()) {
setSelection(0, text().size());
copy();
del();
}
return;
}
QLineEdit::keyPressEvent(e);
if (e->key() == Qt::Key::Key_Backspace) {
emit backspacePressed();
}
}
void PinLineEdit::textEdited() {
if (!d->mFormattedPassphrase) {
return;
}
auto currentText = text();
// first calculate the cursor position in the reformatted text; the cursor
// is put left of the separators, so that backspace works as expected
auto cursorPos = cursorPosition();
cursorPos -= QStringView{currentText}.left(cursorPos).count(
FormattedPassphraseSeparator);
cursorPos += std::max(cursorPos - 1, 0) / FormattedPassphraseGroupSize;
// then reformat the text
currentText.remove(FormattedPassphraseSeparator);
currentText = d->formatted(currentText);
// finally, set reformatted text and updated cursor position
setText(currentText);
setCursorPosition(cursorPos);
}

View File

@ -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 <dev@ingo-kloecker.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef _PINLINEEDIT_H_
#define _PINLINEEDIT_H_
#include <QLineEdit>
class PinLineEdit : public QLineEdit {
Q_OBJECT
public:
explicit PinLineEdit(QWidget *parent = nullptr);
~PinLineEdit() override;
void setPin(const QString &pin);
QString pin() const;
public Q_SLOTS:
void setFormattedPassphrase(bool on);
void copy() const;
void cut();
Q_SIGNALS:
void backspacePressed();
protected:
void keyPressEvent(QKeyEvent *) override;
private:
using QLineEdit::setText;
using QLineEdit::text;
private Q_SLOTS:
void textEdited();
private:
class Private;
std::unique_ptr<Private> d;
};
#endif // _PINLINEEDIT_H_

93
src/m_pinentry/qti18n.cpp Normal file
View File

@ -0,0 +1,93 @@
/* qti18n.cpp - Load qt translations for pinentry.
* Copyright 2021 g10 Code GmbH
* SPDX-FileCopyrightText: 2015 Lukáš Tinkl <ltinkl@redhat.com>
* SPDX-FileCopyrightText: 2021 Ingo Klöcker <kloecker@kde.org>
*
* Copied from k18n under the terms of LGPLv2 or later.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#include <QCoreApplication>
#include <QDebug>
#include <QLibraryInfo>
#include <QLocale>
#include <QTranslator>
static bool loadCatalog(const QString &catalog, const QLocale &locale) {
auto translator = new QTranslator(QCoreApplication::instance());
if (!translator->load(locale, catalog, QString(),
QLatin1String(":/i18n_qt"))) {
qDebug() << "Loading the" << catalog << "catalog failed for locale"
<< locale;
delete translator;
return false;
}
QCoreApplication::instance()->installTranslator(translator);
return true;
}
static bool loadCatalog(const QString &catalog, const QLocale &locale,
const QLocale &fallbackLocale) {
// try to load the catalog for locale
if (loadCatalog(catalog, locale)) {
return true;
}
// if this fails, then try the fallback locale (if it's different from locale)
if (fallbackLocale != locale) {
return loadCatalog(catalog, fallbackLocale);
}
return false;
}
// load global Qt translation, needed in KDE e.g. by lots of builtin dialogs
// (QColorDialog, QFontDialog) that we use
static void loadTranslation(const QString &localeName,
const QString &fallbackLocaleName) {
const QLocale locale{localeName};
const QLocale fallbackLocale{fallbackLocaleName};
// first, try to load the qt_ meta catalog
if (loadCatalog(QStringLiteral("qt_"), locale, fallbackLocale)) {
return;
}
// if loading the meta catalog failed, then try loading the four catalogs
// it depends on, i.e. qtbase, qtscript, qtmultimedia, qtxmlpatterns,
// separately
const auto catalogs = {
QStringLiteral("qtbase_"),
/* QStringLiteral("qtscript_"),
QStringLiteral("qtmultimedia_"),
QStringLiteral("qtxmlpatterns_"), */
};
for (const auto &catalog : catalogs) {
loadCatalog(catalog, locale, fallbackLocale);
}
}
static void load() {
// The way Qt translation system handles plural forms makes it necessary to
// have a translation file which contains only plural forms for `en`. That's
// why we load the `en` translation unconditionally, then load the
// translation for the current locale to overload it.
loadCatalog(QStringLiteral("qt_"), QLocale{QStringLiteral("en")});
const QLocale locale = QLocale::system();
if (locale.name() != QStringLiteral("en")) {
loadTranslation(locale.name(), locale.bcp47Name());
}
}
Q_COREAPP_STARTUP_FUNCTION(load)

91
src/m_pinentry/secmem++.h Normal file
View File

@ -0,0 +1,91 @@
/* STL allocator for secmem
* Copyright (C) 2008 Marc Mutz <marc@kdab.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef __SECMEM_SECMEMPP_H__
#define __SECMEM_SECMEMPP_H__
#include "../secmem/secmem.h"
#include <cstddef>
namespace secmem {
template <typename T>
class alloc {
public:
// type definitions:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
// rebind
template <typename U>
struct rebind {
typedef alloc<U> other;
};
// address
pointer address( reference value ) const {
return &value;
}
const_pointer address( const_reference value ) const {
return &value;
}
// (trivial) ctors and dtors
alloc() {}
alloc( const alloc & ) {}
template <typename U> alloc( const alloc<U> & ) {}
// copy ctor is ok
~alloc() {}
// de/allocation
size_type max_size() const {
return secmem_get_max_size();
}
pointer allocate( size_type n, void * =0 ) {
return static_cast<pointer>( secmem_malloc( n * sizeof(T) ) );
}
void deallocate( pointer p, size_type ) {
secmem_free( p );
}
// de/construct
void construct( pointer p, const T & value ) {
void * loc = p;
new (loc)T(value);
}
void destruct( pointer p ) {
p->~T();
}
};
// equality comparison
template <typename T1,typename T2>
bool operator==( const alloc<T1> &, const alloc<T2> & ) { return true; }
template <typename T1, typename T2>
bool operator!=( const alloc<T1> &, const alloc<T2> & ) { return false; }
}
#endif /* __SECMEM_SECMEMPP_H__ */

View File

@ -100,12 +100,9 @@ auto GFExecuteModule(GFModuleEvent* event) -> int {
auto* task = new VersionCheckTask(); auto* task = new VersionCheckTask();
QObject::connect(task, &VersionCheckTask::SignalUpgradeVersion, QObject::connect(task, &VersionCheckTask::SignalUpgradeVersion,
QThread::currentThread(), [event](const SoftwareVersion&) { QThread::currentThread(), [event](const SoftwareVersion&) {
char** event_argv = static_cast<char**>( GFModuleTriggerModuleEventCallback(
GFAllocateMemory(sizeof(char**) * 1)); event, GFGetModuleID(), 1,
event_argv[0] = DUP("0"); ConvertMapToParams({{"ret", "0"}}));
GFModuleTriggerModuleEventCallback(event, GFGetModuleID(),
1, event_argv);
}); });
QObject::connect(task, &VersionCheckTask::SignalUpgradeVersion, task, QObject::connect(task, &VersionCheckTask::SignalUpgradeVersion, task,
&QObject::deleteLater); &QObject::deleteLater);