diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e6475ffd..292980865 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,114 +1,120 @@ cmake_minimum_required(VERSION 3.16 FATAL_ERROR) set(PIM_VERSION "5.17.44") project(libkleo VERSION ${PIM_VERSION}) set(KF5_MIN_VERSION "5.82.0") if (WIN32) set(KF5_WANT_VERSION "5.70.0") else () set(KF5_WANT_VERSION ${KF5_MIN_VERSION}) endif () find_package(ECM ${KF5_WANT_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) set(LIBKLEO_LIB_VERSION ${PIM_VERSION}) set(QT_REQUIRED_VERSION "5.15.0") set(KDEPIMTEXTEDIT_VERSION "5.17.40") find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets) find_package(KF5I18n ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5WidgetsAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5Completion ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5CoreAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5PimTextEdit ${KDEPIMTEXTEDIT_VERSION} CONFIG) set(GPGMEPP_LIB_VERSION "1.13.1") find_package(Gpgmepp ${GPGMEPP_LIB_VERSION} CONFIG REQUIRED) set_package_properties(Gpgmepp PROPERTIES DESCRIPTION "GpgME++ Library" URL "https://www.gnupg.org" TYPE REQUIRED PURPOSE "GpgME++ is required for OpenPGP support") find_package(QGpgme ${GPGMEPP_LIB_VERSION} CONFIG REQUIRED) message(STATUS "GPGME Version ${Gpgmepp_VERSION}") +if (Gpgmepp_VERSION VERSION_GREATER_EQUAL "1.15.2") + set(GPGMEPP_SUPPORTS_TRUST_SIGNATURES 1) +endif() find_package(Boost 1.34.0) set_package_properties(Boost PROPERTIES DESCRIPTION "Boost C++ Libraries" URL "https://www.boost.org" TYPE REQUIRED PURPOSE "Boost is required for building most KDEPIM applications") set_package_properties(KF5PimTextEdit PROPERTIES DESCRIPTION "A textedit with PIM-specific features." URL "https://commits.kde.org/kpimtextedit" TYPE OPTIONAL PURPOSE "Improved audit log viewer.") ecm_setup_version(PROJECT VARIABLE_PREFIX LIBKLEO VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/libkleo_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5LibkleoConfigVersion.cmake" SOVERSION 5 ) ########### Targets ########### add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0) #add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f00) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x055300) remove_definitions(-DQT_NO_FOREACH) add_definitions(-DQT_NO_EMIT) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Libkleo") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5LibkleoConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5LibkleoConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5LibkleoConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5LibkleoConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5LibkleoTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5LibkleoTargets.cmake NAMESPACE KF5::) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libkleo_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-libkleo.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-libkleo.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + option(USE_UNITY_CMAKE_SUPPORT "Use UNITY cmake support (speedup compile time)" OFF) set(COMPILE_WITH_UNITY_CMAKE_SUPPORT OFF) if (USE_UNITY_CMAKE_SUPPORT) set(COMPILE_WITH_UNITY_CMAKE_SUPPORT ON) endif() add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) endif() ecm_qt_install_logging_categories( EXPORT LIBKLEO FILE libkleo.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) ki18n_install(po) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/config-libkleo.h.cmake b/config-libkleo.h.cmake new file mode 100644 index 000000000..cec4fc9cd --- /dev/null +++ b/config-libkleo.h.cmake @@ -0,0 +1,2 @@ +/* Defined if GpgME++ supports trust signatures */ +#cmakedefine GPGMEPP_SUPPORTS_TRUST_SIGNATURES 1 diff --git a/src/models/useridlistmodel.cpp b/src/models/useridlistmodel.cpp index a5b0e42c1..89d9f908b 100644 --- a/src/models/useridlistmodel.cpp +++ b/src/models/useridlistmodel.cpp @@ -1,347 +1,360 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/useridlistmodel.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Andre Heinecke + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ +#include + #include "useridlistmodel.h" #include "keycache.h" #include "utils/formatting.h" #include #include #include #include #include #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_REMARKS #endif #if GPGMEPP_VERSION >= 0x10E01 // 1.14.1 # define GPGME_USERID_SIGNATURES_ARE_SORTABLE #endif using namespace GpgME; using namespace Kleo; class UIDModelItem { // A uid model item can either be a UserID::Signature or a UserID. // you can find out which it is if the uid or the signature return // null values. (Not null but isNull) // public: explicit UIDModelItem(const UserID::Signature &sig, UIDModelItem *parentItem, bool showRemarks) : mParentItem{parentItem} , mSig{sig} { mItemData << QString::fromUtf8(sig.signerKeyID()) << Formatting::prettyName(sig) << Formatting::prettyEMail(sig) << Formatting::creationDateString(sig) << Formatting::expirationDateString(sig) << Formatting::validityShort(sig) << (sig.isExportable() ? QStringLiteral("✓") : QString()); QString lastNotation; if (showRemarks && parentItem) { for (const auto ¬ation: sig.notations()) { if (notation.name() && !strcmp(notation.name(), "rem@gnupg.org")) { lastNotation = QString::fromUtf8(notation.value()); } } } mItemData << lastNotation; + +#ifdef GPGMEPP_SUPPORTS_TRUST_SIGNATURES + mItemData << Formatting::trustSignatureDomain(sig); +#endif } explicit UIDModelItem(const UserID &uid, UIDModelItem *parentItem) : mParentItem{parentItem} , mUid{uid} { mItemData << Formatting::prettyUserID(uid); } // The root item UIDModelItem() { mItemData << i18n("ID") << i18n("Name") << i18n("E-Mail") << i18n("Valid From") << i18n("Valid Until") << i18n("Status") << i18n("Exportable") << i18n("Tags"); +#ifdef GPGMEPP_SUPPORTS_TRUST_SIGNATURES + mItemData << i18n("Trust Signature For"); +#endif } ~UIDModelItem() { qDeleteAll(mChildItems); } void appendChild(UIDModelItem *child) { mChildItems << child; } UIDModelItem *child(int row) const { return mChildItems.value(row); } const UIDModelItem *constChild(int row) const { return mChildItems.value(row); } int childCount() const { return mChildItems.count(); } int columnCount() const { if (childCount()) { // We take the value from the first child // as we are likely a UID and our children // are UID Signatures. return constChild(0)->columnCount(); } return mItemData.count(); } QVariant data(int column) const { return mItemData.value(column); } QVariant toolTip(int column) const { if (!mSig.isNull()) { if (column == static_cast(UserIDListModel::Column::Status)) { return i18n("class %1", mSig.certClass()); + } else if (column == static_cast(UserIDListModel::Column::TrustSignatureDomain)) { + return Formatting::trustSignature(mSig); } } return mItemData.value(column); } QVariant icon(int column) const { if (!mSig.isNull() && column == static_cast(UserIDListModel::Column::Status)) { return Formatting::validityIcon(mSig); } return {}; } int row() const { if (mParentItem) { return mParentItem->mChildItems.indexOf(const_cast(this)); } return 0; } UIDModelItem *parentItem() const { return mParentItem; } UserID::Signature signature() const { return mSig; } UserID uid() const { return mUid; } private: QList mChildItems; QList mItemData; UIDModelItem *mParentItem = nullptr; UserID::Signature mSig; UserID mUid; }; UserIDListModel::UserIDListModel(QObject *p) : QAbstractItemModel{p} { } UserIDListModel::~UserIDListModel() = default; Key UserIDListModel::key() const { return mKey; } void UserIDListModel::setKey(const Key &key) { beginResetModel(); mKey = key; mRootItem.reset(new UIDModelItem); for (int i = 0, ids = key.numUserIDs(); i < ids; ++i) { UserID uid = key.userID(i); auto uidItem = new UIDModelItem(uid, mRootItem.get()); mRootItem->appendChild(uidItem); std::vector sigs = uid.signatures(); #ifdef GPGME_USERID_SIGNATURES_ARE_SORTABLE std::sort(sigs.begin(), sigs.end()); #endif for (const auto &sig : sigs) { auto sigItem = new UIDModelItem(sig, uidItem, mRemarksEnabled); uidItem->appendChild(sigItem); } } endResetModel(); } int UserIDListModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) { return static_cast(parent.internalPointer())->columnCount(); } if (!mRootItem) { return 0; } return mRootItem->columnCount(); } int UserIDListModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0 || !mRootItem) { return 0; } const UIDModelItem *const parentItem = !parent.isValid() ? mRootItem.get() : static_cast(parent.internalPointer()); return parentItem->childCount(); } QModelIndex UserIDListModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return {}; } const UIDModelItem *const parentItem = !parent.isValid() ? mRootItem.get() : static_cast(parent.internalPointer()); UIDModelItem *const childItem = parentItem->child(row); if (childItem) { return createIndex(row, column, childItem); } else { return QModelIndex(); } } QModelIndex UserIDListModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return {}; } auto childItem = static_cast(index.internalPointer()); UIDModelItem *parentItem = childItem->parentItem(); if (parentItem == mRootItem.get()) { return QModelIndex(); } return createIndex(parentItem->row(), 0, parentItem); } QVariant UserIDListModel::headerData(int section, Qt::Orientation o, int role) const { if (o == Qt::Horizontal && mRootItem) { if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) { return mRootItem->data(section); } } return QVariant(); } QVariant UserIDListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ToolTipRole && role != Qt::DecorationRole) { return QVariant(); } auto item = static_cast(index.internalPointer()); if (role == Qt::ToolTipRole) { return item->toolTip(index.column()); } if (role == Qt::DecorationRole) { return item->icon(index.column()); } return item->data(index.column()); } UserID UserIDListModel::userID(const QModelIndex& index) const { if (!index.isValid()) { return UserID(); } UIDModelItem *item = static_cast(index.internalPointer()); return item->uid(); } QVector UserIDListModel::userIDs(const QModelIndexList &indexs) const { QVector ret; for (const QModelIndex &idx : indexs) { if (!idx.isValid()) { continue; } auto item = static_cast(idx.internalPointer()); if (!item->uid().isNull()) { ret << item->uid(); } } return ret; } UserID::Signature UserIDListModel::signature(const QModelIndex& index) const { if (!index.isValid()) { return UserID::Signature(); } UIDModelItem *item = static_cast(index.internalPointer()); return item->signature(); } QVector UserIDListModel::signatures(const QModelIndexList &indexs) const { QVector ret; for (const QModelIndex &idx : indexs) { if (!idx.isValid()) { continue; } auto item = static_cast(idx.internalPointer()); if (!item->signature().isNull()) { ret << item->signature(); } } return ret; } void UserIDListModel::enableRemarks(bool value) { mRemarksEnabled = value; } diff --git a/src/models/useridlistmodel.h b/src/models/useridlistmodel.h index 0a01c9aac..ca4fc5d29 100644 --- a/src/models/useridlistmodel.h +++ b/src/models/useridlistmodel.h @@ -1,72 +1,75 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/useridlistmodel.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Andre Heinecke + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include // since Signature is nested in UserID... #include class UIDModelItem; namespace Kleo { class KLEO_EXPORT UserIDListModel : public QAbstractItemModel { Q_OBJECT public: enum class Column { Id, Name, Email, ValidFrom, ValidUntil, Status, Exportable, Tags, + TrustSignatureDomain, }; explicit UserIDListModel(QObject *parent = nullptr); ~UserIDListModel() override; GpgME::Key key() const; public: GpgME::UserID userID(const QModelIndex &index) const; QVector userIDs(const QModelIndexList &indexs) const; GpgME::UserID::Signature signature(const QModelIndex &index) const; QVector signatures(const QModelIndexList &indexs) const; void enableRemarks(bool value); public Q_SLOTS: void setKey(const GpgME::Key &key); public: int columnCount(const QModelIndex &pindex = QModelIndex()) const override; int rowCount(const QModelIndex &pindex = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation o, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int col, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; private: GpgME::Key mKey; bool mRemarksEnabled = false; std::unique_ptr mRootItem; }; } diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp index 8a6b9b0c2..8b18b586a 100644 --- a/src/utils/formatting.cpp +++ b/src/utils/formatting.cpp @@ -1,1205 +1,1252 @@ /* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*- - utils/formatting.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ +#include + #include "formatting.h" #include "kleo/dn.h" #include "kleo/keyfiltermanager.h" #include "kleo/keygroup.h" #include "utils/cryptoconfig.h" #include #include #include #include #include #include #include #include #include #include // for Qt::escape #include #include #include #include "models/keycache.h" using namespace GpgME; using namespace Kleo; // // Name // QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_) { if (proto == OpenPGP) { const QString name = QString::fromUtf8(name_); if (name.isEmpty()) { return QString(); } const QString comment = QString::fromUtf8(comment_); if (comment.isEmpty()) { return name; } return QStringLiteral("%1 (%2)").arg(name, comment); } if (proto == CMS) { const DN subject(id); const QString cn = subject[QStringLiteral("CN")].trimmed(); if (cn.isEmpty()) { return subject.prettyDN(); } return cn; } return QString(); } QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_) { return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_)); } QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment) { if (proto == OpenPGP) { if (name.isEmpty()) { if (email.isEmpty()) { return QString(); } else if (comment.isEmpty()) { return QStringLiteral("<%1>").arg(email); } else { return QStringLiteral("(%2) <%1>").arg(email, comment); } } if (email.isEmpty()) { if (comment.isEmpty()) { return name; } else { return QStringLiteral("%1 (%2)").arg(name, comment); } } if (comment.isEmpty()) { return QStringLiteral("%1 <%2>").arg(name, email); } else { return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment); } } if (proto == CMS) { const DN subject(id); const QString cn = subject[QStringLiteral("CN")].trimmed(); if (cn.isEmpty()) { return subject.prettyDN(); } return cn; } return QString(); } QString Formatting::prettyUserID(const UserID &uid) { if (uid.parent().protocol() == OpenPGP) { return prettyNameAndEMail(uid); } const QByteArray id = QByteArray(uid.id()).trimmed(); if (id.startsWith('<')) { return prettyEMail(uid.email(), uid.id()); } if (id.startsWith('(')) // ### parse uri/dns: { return QString::fromUtf8(uid.id()); } else { return DN(uid.id()).prettyDN(); } } QString Formatting::prettyKeyID(const char *id) { if (!id) { return QString(); } return QLatin1String("0x") + QString::fromLatin1(id).toUpper(); } QString Formatting::prettyNameAndEMail(const UserID &uid) { return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment()); } QString Formatting::prettyNameAndEMail(const Key &key) { return prettyNameAndEMail(key.userID(0)); } QString Formatting::prettyName(const Key &key) { return prettyName(key.userID(0)); } QString Formatting::prettyName(const UserID &uid) { return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment()); } QString Formatting::prettyName(const UserID::Signature &sig) { return prettyName(OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment()); } // // EMail // QString Formatting::prettyEMail(const Key &key) { for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) { const QString email = prettyEMail(key.userID(i)); if (!email.isEmpty()) { return email; } } return QString(); } QString Formatting::prettyEMail(const UserID &uid) { return prettyEMail(uid.email(), uid.id()); } QString Formatting::prettyEMail(const UserID::Signature &sig) { return prettyEMail(sig.signerEmail(), sig.signerUserID()); } QString Formatting::prettyEMail(const char *email_, const char *id) { QString email, name, comment; if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_), name, email, comment) == KEmailAddress::AddressOk) { return email; } else { return DN(id)[QStringLiteral("EMAIL")].trimmed(); } } // // Tooltip // namespace { static QString protect_whitespace(QString s) { static const QLatin1Char SP(' '), NBSP('\xA0'); return s.replace(SP, NBSP); } template QString format_row(const QString &field, const T_arg &arg) { return QStringLiteral("%1:%2").arg(protect_whitespace(field), arg); } QString format_row(const QString &field, const QString &arg) { return QStringLiteral("%1:%2").arg(protect_whitespace(field), arg.toHtmlEscaped()); } QString format_row(const QString &field, const char *arg) { return format_row(field, QString::fromUtf8(arg)); } QString format_keytype(const Key &key) { const Subkey subkey = key.subkey(0); if (key.hasSecret()) { return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); } else { return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); } } QString format_subkeytype(const Subkey &subkey) { const auto algo = subkey.publicKeyAlgorithm(); if (algo == Subkey::AlgoECC || algo == Subkey::AlgoECDSA || algo == Subkey::AlgoECDH || algo == Subkey::AlgoEDDSA) { return QString::fromStdString(subkey.algoName()); } return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); } QString format_keyusage(const Key &key) { QStringList capabilities; if (key.canReallySign()) { if (key.isQualified()) { capabilities.push_back(i18n("Signing (Qualified)")); } else { capabilities.push_back(i18n("Signing")); } } if (key.canEncrypt()) { capabilities.push_back(i18n("Encryption")); } if (key.canCertify()) { capabilities.push_back(i18n("Certifying User-IDs")); } if (key.canAuthenticate()) { capabilities.push_back(i18n("SSH Authentication")); } return capabilities.join(QLatin1String(", ")); } QString format_subkeyusage(const Subkey &subkey) { QStringList capabilities; if (subkey.canSign()) { if (subkey.isQualified()) { capabilities.push_back(i18n("Signing (Qualified)")); } else { capabilities.push_back(i18n("Signing")); } } if (subkey.canEncrypt()) { capabilities.push_back(i18n("Encryption")); } if (subkey.canCertify()) { capabilities.push_back(i18n("Certifying User-IDs")); } if (subkey.canAuthenticate()) { capabilities.push_back(i18n("SSH Authentication")); } return capabilities.join(QLatin1String(", ")); } static QString time_t2string(time_t t) { QDateTime dt; dt.setTime_t(t); return QLocale().toString(dt, QLocale::ShortFormat); } static QString make_red(const QString &txt) { return QLatin1String("") + txt.toHtmlEscaped() + QLatin1String(""); } } QString Formatting::toolTip(const Key &key, int flags) { if (flags == 0 || (key.protocol() != CMS && key.protocol() != OpenPGP)) { return QString(); } const Subkey subkey = key.subkey(0); QString result; if (flags & Validity) { if (key.protocol() == OpenPGP || (key.keyListMode() & Validate)) { if (key.isRevoked()) { result = make_red(i18n("Revoked")); } else if (key.isExpired()) { result = make_red(i18n("Expired")); } else if (key.isDisabled()) { result = i18n("Disabled"); } else if (key.keyListMode() & GpgME::Validate) { unsigned int fullyTrusted = 0; for (const auto &uid: key.userIDs()) { if (uid.validity() >= UserID::Validity::Full) { fullyTrusted++; } } if (fullyTrusted == key.numUserIDs()) { result = i18n("All User-IDs are certified."); const auto compliance = complianceStringForKey(key); if (!compliance.isEmpty()) { result += QStringLiteral("
") + compliance; } } else { result = i18np("One User-ID is not certified.", "%1 User-IDs are not certified.", key.numUserIDs() - fullyTrusted); } } else { result = i18n("The validity cannot be checked at the moment."); } } else { result = i18n("The validity cannot be checked at the moment."); } } if (flags == Validity) { return result; } result += QLatin1String(""); if (key.protocol() == CMS) { if (flags & SerialNumber) { result += format_row(i18n("Serial number"), key.issuerSerial()); } if (flags & Issuer) { result += format_row(i18n("Issuer"), key.issuerName()); } } if (flags & UserIDs) { const std::vector uids = key.userIDs(); if (!uids.empty()) result += format_row(key.protocol() == CMS ? i18n("Subject") : i18n("User-ID"), prettyUserID(uids.front())); if (uids.size() > 1) for (auto it = uids.begin() + 1, end = uids.end(); it != end; ++it) if (!it->isRevoked() && !it->isInvalid()) { result += format_row(i18n("a.k.a."), prettyUserID(*it)); } } if (flags & ExpiryDates) { result += format_row(i18n("Created"), time_t2string(subkey.creationTime())); if (key.isExpired()) { result += format_row(i18n("Expired"), time_t2string(subkey.expirationTime())); } else if (!subkey.neverExpires()) { result += format_row(i18n("Expires"), time_t2string(subkey.expirationTime())); } } if (flags & CertificateType) { result += format_row(i18n("Type"), format_keytype(key)); } if (flags & CertificateUsage) { result += format_row(i18n("Usage"), format_keyusage(key)); } if (flags & KeyID) { result += format_row(i18n("Key-ID"), QString::fromLatin1(key.shortKeyID())); } if (flags & Fingerprint) { result += format_row(i18n("Fingerprint"), key.primaryFingerprint()); } if (flags & OwnerTrust) { if (key.protocol() == OpenPGP) { result += format_row(i18n("Certification trust"), ownerTrustShort(key)); } else if (key.isRoot()) { result += format_row(i18n("Trusted issuer?"), key.userID(0).validity() == UserID::Ultimate ? i18n("Yes") : /* else */ i18n("No")); } } if (flags & StorageLocation) { if (const char *card = subkey.cardSerialNumber()) { result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { result += format_row(i18n("Stored"), i18nc("stored...", "on this computer")); } } if (flags & Subkeys) { for (const auto &sub: key.subkeys()) { result += QLatin1String("
"); result += format_row(i18n("Subkey"), sub.fingerprint()); if (sub.isRevoked()) { result += format_row(i18n("Status"), i18n("Revoked")); } else if (sub.isExpired()) { result += format_row(i18n("Status"), i18n("Expired")); } if (flags & ExpiryDates) { result += format_row(i18n("Created"), time_t2string(sub.creationTime())); if (key.isExpired()) { result += format_row(i18n("Expired"), time_t2string(sub.expirationTime())); } else if (!subkey.neverExpires()) { result += format_row(i18n("Expires"), time_t2string(sub.expirationTime())); } } if (flags & CertificateType) { result += format_row(i18n("Type"), format_subkeytype(sub)); } if (flags & CertificateUsage) { result += format_row(i18n("Usage"), format_subkeyusage(sub)); } if (flags & StorageLocation) { if (const char *card = sub.cardSerialNumber()) { result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { result += format_row(i18n("Stored"), i18nc("stored...", "on this computer")); } } } } result += QLatin1String("
"); return result; } namespace { template QString getValidityStatement(const Container &keys) { const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [] (const Key &key) { return key.protocol() == OpenPGP; }); const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [] (const Key &key) { return key.keyListMode() & Validate; }); if (allKeysAreOpenPGP || allKeysAreValidated) { const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad)); if (someKeysAreBad) { return i18n("Some keys are revoked, expired, disabled, or invalid."); } else { const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Formatting::uidsHaveFullValidity); if (allKeysAreFullyValid) { return i18n("All keys are certified."); } else { return i18n("Some keys are not certified."); } } } return i18n("The validity of the keys cannot be checked at the moment."); } } QString Formatting::toolTip(const KeyGroup &group, int flags) { static const unsigned int maxNumKeysForTooltip = 20; if (group.isNull()) { return QString(); } const KeyGroup::Keys &keys = group.keys(); if (keys.size() == 0) { return i18nc("@info:tooltip", "This group does not contain any keys."); } const QString validity = (flags & Validity) ? getValidityStatement(keys) : QString(); if (flags == Validity) { return validity; } // list either up to maxNumKeysForTooltip keys or (maxNumKeysForTooltip-1) keys followed by "and n more keys" const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size(); QStringList result; result.reserve(3 + 2 + numKeysForTooltip + 2); if (!validity.isEmpty()) { result.push_back(QStringLiteral("

")); result.push_back(validity.toHtmlEscaped()); result.push_back(QStringLiteral("

")); } result.push_back(QStringLiteral("

")); result.push_back(i18n("Keys:")); { auto it = keys.cbegin(); for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) { result.push_back(QLatin1String("
") + Formatting::summaryLine(*it).toHtmlEscaped()); } } if (keys.size() > numKeysForTooltip) { result.push_back(QLatin1String("
") + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip)); } result.push_back(QStringLiteral("

")); return result.join(QLatin1Char('\n')); } // // Creation and Expiration // namespace { static QDate time_t2date(time_t t) { if (!t) { return {}; } QDateTime dt; dt.setTime_t(t); return dt.date(); } static QString date2string(const QDate &date) { return QLocale().toString(date, QLocale::ShortFormat); } template QString expiration_date_string(const T &tee) { return tee.neverExpires() ? QString() : date2string(time_t2date(tee.expirationTime())); } template QDate creation_date(const T &tee) { return time_t2date(tee.creationTime()); } template QDate expiration_date(const T &tee) { return time_t2date(tee.expirationTime()); } } QString Formatting::dateString(time_t t) { return date2string(time_t2date(t)); } QString Formatting::expirationDateString(const Key &key) { return expiration_date_string(key.subkey(0)); } QString Formatting::expirationDateString(const Subkey &subkey) { return expiration_date_string(subkey); } QString Formatting::expirationDateString(const UserID::Signature &sig) { return expiration_date_string(sig); } QDate Formatting::expirationDate(const Key &key) { return expiration_date(key.subkey(0)); } QDate Formatting::expirationDate(const Subkey &subkey) { return expiration_date(subkey); } QDate Formatting::expirationDate(const UserID::Signature &sig) { return expiration_date(sig); } QString Formatting::creationDateString(const Key &key) { return date2string(creation_date(key.subkey(0))); } QString Formatting::creationDateString(const Subkey &subkey) { return date2string(creation_date(subkey)); } QString Formatting::creationDateString(const UserID::Signature &sig) { return date2string(creation_date(sig)); } QDate Formatting::creationDate(const Key &key) { return creation_date(key.subkey(0)); } QDate Formatting::creationDate(const Subkey &subkey) { return creation_date(subkey); } QDate Formatting::creationDate(const UserID::Signature &sig) { return creation_date(sig); } // // Types // QString Formatting::displayName(Protocol p) { if (p == CMS) { return i18nc("X.509/CMS encryption standard", "S/MIME"); } if (p == OpenPGP) { return i18n("OpenPGP"); } return i18nc("Unknown encryption protocol", "Unknown"); } QString Formatting::type(const Key &key) { return displayName(key.protocol()); } QString Formatting::type(const Subkey &subkey) { return QString::fromUtf8(subkey.publicKeyAlgorithmAsString()); } QString Formatting::type(const KeyGroup &group) { Q_UNUSED(group) return i18nc("a group of keys/certificates", "Group"); } // // Status / Validity // QString Formatting::ownerTrustShort(const Key &key) { return ownerTrustShort(key.ownerTrust()); } QString Formatting::ownerTrustShort(Key::OwnerTrust trust) { switch (trust) { case Key::Unknown: return i18nc("unknown trust level", "unknown"); case Key::Never: return i18n("untrusted"); case Key::Marginal: return i18nc("marginal trust", "marginal"); case Key::Full: return i18nc("full trust", "full"); case Key::Ultimate: return i18nc("ultimate trust", "ultimate"); case Key::Undefined: return i18nc("undefined trust", "undefined"); default: Q_ASSERT(!"unexpected owner trust value"); break; } return QString(); } QString Formatting::validityShort(const Subkey &subkey) { if (subkey.isRevoked()) { return i18n("revoked"); } if (subkey.isExpired()) { return i18n("expired"); } if (subkey.isDisabled()) { return i18n("disabled"); } if (subkey.isInvalid()) { return i18n("invalid"); } return i18nc("as in good/valid signature", "good"); } QString Formatting::validityShort(const UserID &uid) { if (uid.isRevoked()) { return i18n("revoked"); } if (uid.isInvalid()) { return i18n("invalid"); } switch (uid.validity()) { case UserID::Unknown: return i18nc("unknown trust level", "unknown"); case UserID::Undefined: return i18nc("undefined trust", "undefined"); case UserID::Never: return i18n("untrusted"); case UserID::Marginal: return i18nc("marginal trust", "marginal"); case UserID::Full: return i18nc("full trust", "full"); case UserID::Ultimate: return i18nc("ultimate trust", "ultimate"); } return QString(); } QString Formatting::validityShort(const UserID::Signature &sig) { switch (sig.status()) { case UserID::Signature::NoError: if (!sig.isInvalid()) { /* See RFC 4880 Section 5.2.1 */ switch (sig.certClass()) { case 0x10: /* Generic */ case 0x11: /* Persona */ case 0x12: /* Casual */ case 0x13: /* Positive */ return i18n("valid"); case 0x30: return i18n("revoked"); default: return i18n("class %1", sig.certClass()); } } Q_FALLTHROUGH(); // fall through: case UserID::Signature::GeneralError: return i18n("invalid"); case UserID::Signature::SigExpired: return i18n("expired"); case UserID::Signature::KeyExpired: return i18n("certificate expired"); case UserID::Signature::BadSignature: return i18nc("fake/invalid signature", "bad"); case UserID::Signature::NoPublicKey: { /* GnuPG returns the same error for no public key as for expired * or revoked certificates. */ const auto key = KeyCache::instance()->findByKeyIDOrFingerprint (sig.signerKeyID()); if (key.isNull()) { return i18n("no public key"); } else if (key.isExpired()) { return i18n("key expired"); } else if (key.isRevoked()) { return i18n("key revoked"); } else if (key.isDisabled()) { return i18n("key disabled"); } /* can't happen */ return QStringLiteral("unknown"); } } return QString(); } QIcon Formatting::validityIcon(const UserID::Signature &sig) { switch (sig.status()) { case UserID::Signature::NoError: if (!sig.isInvalid()) { /* See RFC 4880 Section 5.2.1 */ switch (sig.certClass()) { case 0x10: /* Generic */ case 0x11: /* Persona */ case 0x12: /* Casual */ case 0x13: /* Positive */ return QIcon::fromTheme(QStringLiteral("emblem-success")); case 0x30: return QIcon::fromTheme(QStringLiteral("emblem-error")); default: return QIcon(); } } Q_FALLTHROUGH(); // fall through: case UserID::Signature::BadSignature: case UserID::Signature::GeneralError: return QIcon::fromTheme(QStringLiteral("emblem-error")); case UserID::Signature::SigExpired: case UserID::Signature::KeyExpired: return QIcon::fromTheme(QStringLiteral("emblem-information")); case UserID::Signature::NoPublicKey: return QIcon::fromTheme(QStringLiteral("emblem-question")); } return QIcon(); } QString Formatting::formatKeyLink(const Key &key) { if (key.isNull()) { return QString(); } return QStringLiteral("%2").arg(QLatin1String(key.primaryFingerprint()), Formatting::prettyName(key)); } QString Formatting::formatForComboBox(const GpgME::Key &key) { const QString name = prettyName(key); QString mail = prettyEMail(key); if (!mail.isEmpty()) { mail = QLatin1Char('<') + mail + QLatin1Char('>'); } return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1String(key.shortKeyID())).simplified(); } namespace { static QString keyToString(const Key &key) { Q_ASSERT(!key.isNull()); const QString email = Formatting::prettyEMail(key); const QString name = Formatting::prettyName(key); if (name.isEmpty()) { return email; } else if (email.isEmpty()) { return name; } else { return QStringLiteral("%1 <%2>").arg(name, email); } } } const char *Formatting::summaryToString(const Signature::Summary summary) { if (summary & Signature::Red) { return "RED"; } if (summary & Signature::Green) { return "GREEN"; } return "YELLOW"; } QString Formatting::signatureToString(const Signature &sig, const Key &key) { if (sig.isNull()) { return QString(); } const bool red = (sig.summary() & Signature::Red); const bool valid = (sig.summary() & Signature::Valid); if (red) if (key.isNull()) if (const char *fpr = sig.fingerprint()) { return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString())); } else { return i18n("Bad signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString())); } else { return i18n("Bad signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString())); } else if (valid) if (key.isNull()) if (const char *fpr = sig.fingerprint()) { return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr)); } else { return i18n("Good signature by an unknown certificate."); } else { return i18n("Good signature by %1.", keyToString(key)); } else if (key.isNull()) if (const char *fpr = sig.fingerprint()) { return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString())); } else { return i18n("Invalid signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString())); } else { return i18n("Invalid signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString())); } } // // ImportResult // QString Formatting::importMetaData(const Import &import, const QStringList &ids) { const QString result = importMetaData(import); if (result.isEmpty()) { return QString(); } else return result + QLatin1Char('\n') + i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') + ids.join(QLatin1Char('\n')); } QString Formatting::importMetaData(const Import &import) { if (import.isNull()) { return QString(); } if (import.error().isCanceled()) { return i18n("The import of this certificate was canceled."); } if (import.error()) return i18n("An error occurred importing this certificate: %1", QString::fromLocal8Bit(import.error().asString())); const unsigned int status = import.status(); if (status & Import::NewKey) return (status & Import::ContainedSecretKey) ? i18n("This certificate was new to your keystore. The secret key is available.") : i18n("This certificate is new to your keystore."); QStringList results; if (status & Import::NewUserIDs) { results.push_back(i18n("New user-ids were added to this certificate by the import.")); } if (status & Import::NewSignatures) { results.push_back(i18n("New signatures were added to this certificate by the import.")); } if (status & Import::NewSubkeys) { results.push_back(i18n("New subkeys were added to this certificate by the import.")); } return results.empty() ? i18n("The import contained no new data for this certificate. It is unchanged.") : results.join(QLatin1Char('\n')); } // // Overview in CertificateDetailsDialog // QString Formatting::formatOverview(const Key &key) { return toolTip(key, AllOptions); } QString Formatting::usageString(const Subkey &sub) { QStringList usageStrings; if (sub.canCertify()) { usageStrings << i18n("Certify"); } if (sub.canSign()) { usageStrings << i18n("Sign"); } if (sub.canEncrypt()) { usageStrings << i18n("Encrypt"); } if (sub.canAuthenticate()) { usageStrings << i18n("Authenticate"); } return usageStrings.join(QLatin1String(", ")); } QString Formatting::summaryLine(const Key &key) { return keyToString(key) + QLatin1Char(' ') + i18nc("(validity, protocol, creation date)", "(%1, %2, created: %3)", Formatting::complianceStringShort(key), displayName(key.protocol()), Formatting::creationDateString(key)); } QString Formatting::summaryLine(const KeyGroup &group) { switch (group.source()) { case KeyGroup::ApplicationConfig: case KeyGroup::GnuPGConfig: return i18ncp("name of group of keys (n key(s), validity)", "%2 (1 key, %3)", "%2 (%1 keys, %3)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); case KeyGroup::Tags: return i18ncp("name of group of keys (n key(s), validity, tag)", "%2 (1 key, %3, tag)", "%2 (%1 keys, %3, tag)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); default: return i18ncp("name of group of keys (n key(s), validity, group ...)", "%2 (1 key, %3, unknown origin)", "%2 (%1 keys, %3, unknown origin)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); } } namespace { QIcon iconForValidity(UserID::Validity validity) { switch (validity) { case UserID::Ultimate: case UserID::Full: case UserID::Marginal: return QIcon::fromTheme(QStringLiteral("emblem-success")); case UserID::Never: return QIcon::fromTheme(QStringLiteral("emblem-error")); case UserID::Undefined: case UserID::Unknown: default: return QIcon::fromTheme(QStringLiteral("emblem-information")); } } } // Icon for certificate selection indication QIcon Formatting::iconForUid(const UserID &uid) { return iconForValidity(uid.validity()); } QString Formatting::validity(const UserID &uid) { switch (uid.validity()) { case UserID::Ultimate: return i18n("The certificate is marked as your own."); case UserID::Full: return i18n("The certificate belongs to this recipient."); case UserID::Marginal: return i18n("The trust model indicates marginally that the certificate belongs to this recipient."); case UserID::Never: return i18n("This certificate should not be used."); case UserID::Undefined: case UserID::Unknown: default: return i18n("There is no indication that this certificate belongs to this recipient."); } } QString Formatting::validity(const KeyGroup &group) { if (group.isNull()) { return QString(); } const KeyGroup::Keys &keys = group.keys(); if (keys.size() == 0) { return i18n("This group does not contain any keys."); } return getValidityStatement(keys); } namespace { UserID::Validity minimalValidityOfNotRevokedUserIDs(const Key &key) { std::vector userIDs = key.userIDs(); const auto endOfNotRevokedUserIDs = std::remove_if(userIDs.begin(), userIDs.end(), std::mem_fn(&UserID::isRevoked)); const int minValidity = std::accumulate(userIDs.begin(), endOfNotRevokedUserIDs, UserID::Ultimate + 1, [] (int validity, const UserID &userID) { return std::min(validity, static_cast(userID.validity())); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } template UserID::Validity minimalValidity(const Container& keys) { const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [] (int validity, const Key &key) { return std::min(validity, minimalValidityOfNotRevokedUserIDs(key)); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } } QIcon Formatting::validityIcon(const KeyGroup &group) { return iconForValidity(minimalValidity(group.keys())); } bool Formatting::uidsHaveFullValidity(const Key &key) { return minimalValidityOfNotRevokedUserIDs(key) >= UserID::Full; } QString Formatting::complianceMode() { const auto complianceValue = getCryptoConfigStringValue("gpg", "compliance"); return complianceValue == QLatin1String("gnupg") ? QString() : complianceValue; } bool Formatting::isKeyDeVs(const GpgME::Key &key) { for (const auto &sub: key.subkeys()) { if (sub.isExpired() || sub.isRevoked()) { // Ignore old subkeys continue; } if (!sub.isDeVs()) { return false; } } return true; } QString Formatting::complianceStringForKey(const GpgME::Key &key) { // There will likely be more in the future for other institutions // for now we only have DE-VS if (complianceMode() == QLatin1String("de-vs")) { if (uidsHaveFullValidity(key) && isKeyDeVs(key)) { return i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "May be used for %1 communication.", deVsString()); } else { return i18nc("VS-NfD-conforming is a German standard for restricted documents. For which special restrictions about algorithms apply. The string describes if a key is compliant to that..", "May not be used for %1 communication.", deVsString()); } } return QString(); } QString Formatting::complianceStringShort(const GpgME::Key &key) { const bool keyValidityChecked = (key.keyListMode() & GpgME::Validate); if (keyValidityChecked && Formatting::uidsHaveFullValidity(key)) { if (complianceMode() == QLatin1String("de-vs") && Formatting::isKeyDeVs(key)) { return QStringLiteral("★ ") + deVsString(true); } return i18nc("As in all user IDs are valid.", "certified"); } if (key.isExpired()) { return i18n("expired"); } if (key.isRevoked()) { return i18n("revoked"); } if (key.isDisabled()) { return i18n("disabled"); } if (key.isInvalid()) { return i18n("invalid"); } if (keyValidityChecked) { return i18nc("As in not all user IDs are valid.", "not certified"); } return i18nc("The validity of the user IDs has not been/could not be checked", "not checked"); } QString Formatting::complianceStringShort(const KeyGroup &group) { const KeyGroup::Keys &keys = group.keys(); const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Formatting::uidsHaveFullValidity); if (allKeysFullyValid) { return i18nc("As in all keys are valid.", "all certified"); } return i18nc("As in not all keys are valid.", "not all certified"); } QString Formatting::prettyID(const char *id) { if (!id) { return QString(); } QString ret = QString::fromLatin1(id).toUpper().replace(QRegularExpression(QStringLiteral("(....)")), QStringLiteral("\\1 ")).trimmed(); // For the standard 10 group fingerprint let us use a double space in the // middle to increase readability if (ret.size() == 49) { ret.insert(24, QLatin1Char(' ')); } return ret; } QString Formatting::origin(int o) { switch (o) { case Key::OriginKS: return i18n("Keyserver"); case Key::OriginDane: return QStringLiteral("DANE"); case Key::OriginWKD: return QStringLiteral("WKD"); case Key::OriginURL: return QStringLiteral("URL"); case Key::OriginFile: return i18n("File import"); case Key::OriginSelf: return i18n("Generated"); case Key::OriginOther: case Key::OriginUnknown: default: return i18n("Unknown"); } } QString Formatting::deVsString(bool compliant) { const auto filter = KeyFilterManager::instance()->keyFilterByID(compliant ? QStringLiteral("de-vs-filter") : QStringLiteral("not-de-vs-filter")); if (!filter) { return compliant ? i18n("VS-NfD compliant") : i18n("Not VS-NfD compliant"); } return filter->name(); } + +namespace +{ +QString formatTrustScope(const char *trustScope) +{ + static const QRegularExpression escapedNonAlphaNum{QStringLiteral(R"(\\([^0-9A-Za-z]))")}; + + const auto scopeRegExp = QString::fromUtf8(trustScope); + if (scopeRegExp.startsWith(u"<[^>]+[@.]") && scopeRegExp.endsWith(u">$")) { + // looks like a trust scope regular expression created by gpg + auto domain = scopeRegExp.mid(10, scopeRegExp.size() - 10 - 2); + domain.replace(escapedNonAlphaNum, QStringLiteral(R"(\1)")); + return domain; + } + return scopeRegExp; +} +} + +QString Formatting::trustSignatureDomain(const GpgME::UserID::Signature &sig) +{ +#ifdef GPGMEPP_SUPPORTS_TRUST_SIGNATURES + return formatTrustScope(sig.trustScope()); +#else + return {}; +#endif +} + +QString Formatting::trustSignature(const GpgME::UserID::Signature &sig) +{ +#ifdef GPGMEPP_SUPPORTS_TRUST_SIGNATURES + switch (sig.trustValue()) { + case TrustSignatureTrust::Partial: + return i18nc("Certifies this key as partially trusted introducer for 'domain name'.", + "Certifies this key as partially trusted introducer for '%1'.", trustSignatureDomain(sig)); + case TrustSignatureTrust::Complete: + return i18nc("Certifies this key as fully trusted introducer for 'domain name'.", + "Certifies this key as fully trusted introducer for '%1'.", trustSignatureDomain(sig)); + default: + return {}; + } +#else + return {}; +#endif +} diff --git a/src/utils/formatting.h b/src/utils/formatting.h index 4407ffdb3..361cf494c 100644 --- a/src/utils/formatting.h +++ b/src/utils/formatting.h @@ -1,158 +1,165 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/formatting.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include class QString; #include class QDate; class QIcon; namespace GpgME { class Import; } namespace Kleo { class KeyGroup; namespace Formatting { KLEO_EXPORT QString prettyNameAndEMail(int proto, const char *id, const char *name, const char *email, const char *comment); KLEO_EXPORT QString prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment); KLEO_EXPORT QString prettyNameAndEMail(const GpgME::Key &key); KLEO_EXPORT QString prettyNameAndEMail(const GpgME::UserID &key); KLEO_EXPORT QString prettyUserID(const GpgME::UserID &uid); KLEO_EXPORT QString prettyKeyID(const char *id); KLEO_EXPORT QString prettyName(int proto, const char *id, const char *name, const char *comment); KLEO_EXPORT QString prettyName(const GpgME::Key &key); KLEO_EXPORT QString prettyName(const GpgME::UserID &uid); KLEO_EXPORT QString prettyName(const GpgME::UserID::Signature &sig); KLEO_EXPORT QString prettyEMail(const char *email, const char *id); KLEO_EXPORT QString prettyEMail(const GpgME::Key &key); KLEO_EXPORT QString prettyEMail(const GpgME::UserID &uid); KLEO_EXPORT QString prettyEMail(const GpgME::UserID::Signature &sig); /* Formats a fingerprint or keyid into groups of four */ KLEO_EXPORT QString prettyID(const char *id); enum ToolTipOption { KeyID = 0x001, Validity = 0x002, StorageLocation = 0x004, SerialNumber = 0x008, Issuer = 0x010, Subject = 0x020, ExpiryDates = 0x040, CertificateType = 0x080, CertificateUsage = 0x100, Fingerprint = 0x200, UserIDs = 0x400, OwnerTrust = 0x800, Subkeys = 0x1000, AllOptions = 0xffff }; KLEO_EXPORT QString toolTip(const GpgME::Key &key, int opts); KLEO_EXPORT QString toolTip(const Kleo::KeyGroup &group, int opts); KLEO_EXPORT QString expirationDateString(const GpgME::Key &key); KLEO_EXPORT QString expirationDateString(const GpgME::Subkey &subkey); KLEO_EXPORT QString expirationDateString(const GpgME::UserID::Signature &sig); KLEO_EXPORT QDate expirationDate(const GpgME::Key &key); KLEO_EXPORT QDate expirationDate(const GpgME::Subkey &subkey); KLEO_EXPORT QDate expirationDate(const GpgME::UserID::Signature &sig); KLEO_EXPORT QString creationDateString(const GpgME::Key &key); KLEO_EXPORT QString creationDateString(const GpgME::Subkey &subkey); KLEO_EXPORT QString creationDateString(const GpgME::UserID::Signature &sig); KLEO_EXPORT QDate creationDate(const GpgME::Key &key); KLEO_EXPORT QDate creationDate(const GpgME::Subkey &subkey); KLEO_EXPORT QDate creationDate(const GpgME::UserID::Signature &sig); /* Convert a GPGME style time to a localized string */ KLEO_EXPORT QString dateString(time_t t); KLEO_EXPORT QString displayName(GpgME::Protocol prot); KLEO_EXPORT QString type(const GpgME::Key &key); KLEO_EXPORT QString type(const GpgME::Subkey &subkey); KLEO_EXPORT QString type(const Kleo::KeyGroup &group); KLEO_EXPORT QString ownerTrustShort(const GpgME::Key &key); KLEO_EXPORT QString ownerTrustShort(GpgME::Key::OwnerTrust trust); KLEO_EXPORT QString validityShort(const GpgME::Subkey &subkey); KLEO_EXPORT QString validityShort(const GpgME::UserID &uid); KLEO_EXPORT QString validityShort(const GpgME::UserID::Signature &sig); KLEO_EXPORT QIcon validityIcon(const GpgME::UserID::Signature &sig); /* A sentence about the validity of the UserID */ KLEO_EXPORT QString validity(const GpgME::UserID &uid); KLEO_EXPORT QString validity(const Kleo::KeyGroup &group); KLEO_EXPORT QIcon validityIcon(const Kleo::KeyGroup &group); KLEO_EXPORT QString formatForComboBox(const GpgME::Key &key); KLEO_EXPORT QString formatKeyLink(const GpgME::Key &key); KLEO_EXPORT QString signatureToString(const GpgME::Signature &sig, const GpgME::Key &key); KLEO_EXPORT const char *summaryToString(const GpgME::Signature::Summary summary); KLEO_EXPORT QString importMetaData(const GpgME::Import &import); KLEO_EXPORT QString importMetaData(const GpgME::Import &import, const QStringList &sources); KLEO_EXPORT QString formatOverview(const GpgME::Key &key); KLEO_EXPORT QString usageString(const GpgME::Subkey &subkey); KLEO_EXPORT QString summaryLine(const GpgME::Key &key); KLEO_EXPORT QString summaryLine(const KeyGroup &group); KLEO_EXPORT QIcon iconForUid(const GpgME::UserID &uid); /* Is the key valid i.e. are all uids fully trusted? */ KLEO_EXPORT bool uidsHaveFullValidity(const GpgME::Key &key); /* The compliance mode of the gnupg system. Empty if compliance * mode is not set. */ KLEO_EXPORT QString complianceMode(); /* Is the given key in compliance with CO_DE_VS? */ KLEO_EXPORT bool isKeyDeVs(const GpgME::Key &key); /* Localized string describing the name of the VS-NfD Compliance filter. If * compliant is false the name of the not Compliant filter. * * This is required to make the string configurable which is * a common request from users because VS-NfD compliance is called * differently in different enviornments. E.g NATO RESTRICTED or * EU RESTRICTED. */ KLEO_EXPORT QString deVsString (bool compliant = true); /* A sentence if the key confirms to the current compliance mode */ KLEO_EXPORT QString complianceStringForKey(const GpgME::Key &key); /* A single word for use in keylists to describe the validity of the * given key, including any conformance statements relevant to the * current conformance mode. */ KLEO_EXPORT QString complianceStringShort(const GpgME::Key &key); KLEO_EXPORT QString complianceStringShort(const Kleo::KeyGroup &group); /* The origin of the key mapped to a localized string */ KLEO_EXPORT QString origin(int o); + +/* Human-readable trust signature scope (for trust signature regexp created by GnuPG) */ +KLEO_EXPORT QString trustSignatureDomain(const GpgME::UserID::Signature &sig); +/* Summary of trust signature properties */ +KLEO_EXPORT QString trustSignature(const GpgME::UserID::Signature &sig); } }