Page MenuHome GnuPG

No OneTemporary

diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp
index a22082d6..09d1eef9 100644
--- a/src/utils/formatting.cpp
+++ b/src/utils/formatting.cpp
@@ -1,1260 +1,1291 @@
/* -*- 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-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "formatting.h"
#include "kleo/dn.h"
#include "kleo/keyfiltermanager.h"
#include "kleo/keygroup.h"
#include "utils/cryptoconfig.h"
#include "utils/gnupg.h"
#include <gpgme++/key.h>
#include <gpgme++/importresult.h>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <KLocalizedString>
#include <KEmailAddress>
#include <QString>
#include <QDateTime>
#include <QTextDocument> // for Qt::escape
#include <QLocale>
#include <QIcon>
#include <QRegularExpression>
#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 == GpgME::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 == GpgME::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 == GpgME::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 == GpgME::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() == GpgME::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(GpgME::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;
QString name;
QString 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(' ');
static const QLatin1Char NBSP('\xA0');
return s.replace(SP, NBSP);
}
template <typename T_arg>
QString format_row(const QString &field, const T_arg &arg)
{
return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg);
}
QString format_row(const QString &field, const QString &arg)
{
return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").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("<font color=\"red\">") + txt.toHtmlEscaped() + QLatin1String("</font>");
}
}
QString Formatting::toolTip(const Key &key, int flags)
{
if (flags == 0 || (key.protocol() != GpgME::CMS && key.protocol() != GpgME::OpenPGP)) {
return QString();
}
const Subkey subkey = key.subkey(0);
QString result;
if (flags & Validity) {
if (key.protocol() == GpgME::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("<br>") + 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("<table border=\"0\">");
if (key.protocol() == GpgME::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<UserID> uids = key.userIDs();
if (!uids.empty()) {
result += format_row(key.protocol() == GpgME::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() == GpgME::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("<hr/>");
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("</table>");
return result;
}
namespace
{
template <typename Container>
QString getValidityStatement(const Container &keys)
{
const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [] (const Key &key) { return key.protocol() == GpgME::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("<p>"));
result.push_back(validity.toHtmlEscaped());
result.push_back(QStringLiteral("</p>"));
}
result.push_back(QStringLiteral("<p>"));
result.push_back(i18n("Keys:"));
{
auto it = keys.cbegin();
for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) {
result.push_back(QLatin1String("<br>") + Formatting::summaryLine(*it).toHtmlEscaped());
}
}
if (keys.size() > numKeysForTooltip) {
result.push_back(QLatin1String("<br>") + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip));
}
result.push_back(QStringLiteral("</p>"));
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);
}
+static QString localized_long_format(const QDate &date)
+{
+ return date.isValid() ? QLocale().toString(date, QLocale::LongFormat) : QString{};
+}
template <typename T>
QString expiration_date_string(const T &tee)
{
return tee.neverExpires() ? QString() : date2string(time_t2date(tee.expirationTime()));
}
template <typename T>
QDate creation_date(const T &tee)
{
return time_t2date(tee.creationTime());
}
template <typename T>
QDate expiration_date(const T &tee)
{
return time_t2date(tee.expirationTime());
}
}
QString Formatting::dateString(time_t t)
{
return date2string(time_t2date(t));
}
+QString Formatting::accessibleDate(time_t t)
+{
+ return localized_long_format(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::accessibleExpirationDate(const Key &key)
+{
+ return key.subkey(0).neverExpires() ? i18n("no expiration") : localized_long_format(expirationDate(key));
+}
+
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);
}
+QString Formatting::accessibleCreationDate(const Key &key)
+{
+ return localized_long_format(creationDate(key));
+}
+
//
// Types
//
QString Formatting::displayName(GpgME::Protocol p)
{
if (p == GpgME::CMS) {
return i18nc("X.509/CMS encryption standard", "S/MIME");
}
if (p == GpgME::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("<a href=\"key:%1\">%2</a>").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<UserID> 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<int>(userID.validity()));
});
return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
}
template <typename Container>
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<int>(validity, minimalValidityOfNotRevokedUserIDs(key));
});
return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(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 (Kleo::gnupgIsDeVsCompliant()) {
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 <b>not</b> 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 (Kleo::gnupgIsDeVsCompliant() && 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::accessibleHexID(const char *id)
+{
+ static const QRegularExpression groupOfFourRegExp{QStringLiteral("(?:(.)(.)(.)(.))")};
+
+ QString ret;
+ ret = QString::fromLatin1(id);
+ if (!ret.isEmpty() && (ret.size() % 4 == 0)) {
+ ret = ret.replace(groupOfFourRegExp, QStringLiteral("\\1 \\2 \\3 \\4, ")).chopped(2);
+ }
+ 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 388452ab..c7948f85 100644
--- a/src/utils/formatting.h
+++ b/src/utils/formatting.h
@@ -1,167 +1,171 @@
/* -*- 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-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <gpgme++/key.h>
#include "kleo_export.h"
class QString;
#include <QStringList>
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);
+KLEO_EXPORT QString accessibleHexID(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 accessibleExpirationDate(const GpgME::Key &key);
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);
+KLEO_EXPORT QString accessibleCreationDate(const GpgME::Key &key);
/* Convert a GPGME style time to a localized string */
KLEO_EXPORT QString dateString(time_t t);
+KLEO_EXPORT QString accessibleDate(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.
* Use Kleo::gnupgComplianceMode() instead.
*/
KLEO_DEPRECATED_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 environments. 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);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 23, 2:10 PM (16 h, 53 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
09/88/6d2bb8c69c4e739c819b675ec84c

Event Timeline