File indexing completed on 2025-01-05 04:55:52
0001 /* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*- 0002 utils/formatting.cpp 0003 0004 This file is part of Kleopatra, the KDE keymanager 0005 SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB 0006 SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH 0007 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include <config-libkleo.h> 0013 0014 #include "formatting.h" 0015 0016 #include "algorithm.h" 0017 #include "compat.h" 0018 #include "compliance.h" 0019 #include "cryptoconfig.h" 0020 #include "gnupg.h" 0021 #include "keyhelpers.h" 0022 0023 #include <libkleo/dn.h> 0024 #include <libkleo/keycache.h> 0025 #include <libkleo/keygroup.h> 0026 0027 #include <libkleo_debug.h> 0028 0029 #include <KEmailAddress> 0030 #include <KLocalizedString> 0031 0032 #include <QGpgME/CryptoConfig> 0033 #include <QGpgME/Protocol> 0034 0035 #include <QDateTime> 0036 #include <QIcon> 0037 #include <QLocale> 0038 #include <QRegularExpression> 0039 #include <QString> 0040 #include <QTextDocument> // for Qt::escape 0041 0042 #include <gpgme++/importresult.h> 0043 #include <gpgme++/key.h> 0044 0045 #include <gpg-error.h> 0046 0047 using namespace GpgME; 0048 using namespace Kleo; 0049 0050 namespace 0051 { 0052 QIcon iconForValidityAndCompliance(UserID::Validity validity, bool isCompliant) 0053 { 0054 switch (validity) { 0055 case UserID::Ultimate: 0056 case UserID::Full: 0057 case UserID::Marginal: 0058 return isCompliant ? Formatting::successIcon() : Formatting::infoIcon(); 0059 case UserID::Never: 0060 return Formatting::errorIcon(); 0061 case UserID::Undefined: 0062 case UserID::Unknown: 0063 default: 0064 return Formatting::infoIcon(); 0065 } 0066 } 0067 QIcon iconForValidity(const UserID &userId) 0068 { 0069 const bool keyIsCompliant = !DeVSCompliance::isActive() || // 0070 (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(userId.parent())); 0071 return iconForValidityAndCompliance(userId.validity(), keyIsCompliant); 0072 } 0073 } 0074 0075 QIcon Formatting::IconProvider::icon(const GpgME::Key &key) const 0076 { 0077 return icon(key.userID(0)); 0078 } 0079 0080 QIcon Formatting::IconProvider::icon(const GpgME::UserID &userID) const 0081 { 0082 if (usage.canEncrypt() && !Kleo::canBeUsedForEncryption(userID.parent())) { 0083 return Formatting::errorIcon(); 0084 } 0085 if (usage.canSign() && !Kleo::canBeUsedForSigning(userID.parent())) { 0086 return Formatting::errorIcon(); 0087 } 0088 if (userID.parent().isBad() || userID.isBad()) { 0089 return Formatting::errorIcon(); 0090 } 0091 if (Kleo::isRevokedOrExpired(userID)) { 0092 return Formatting::errorIcon(); 0093 } 0094 return iconForValidity(userID); 0095 } 0096 0097 QIcon Formatting::IconProvider::icon(const KeyGroup &group) const 0098 { 0099 if (usage.canEncrypt() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForEncryption)) { 0100 return Formatting::errorIcon(); 0101 } 0102 if (usage.canSign() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForSigning)) { 0103 return Formatting::errorIcon(); 0104 } 0105 return validityIcon(group); 0106 } 0107 0108 QIcon Formatting::successIcon() 0109 { 0110 return QIcon::fromTheme(QStringLiteral("emblem-success")); 0111 } 0112 0113 QIcon Formatting::infoIcon() 0114 { 0115 return QIcon::fromTheme(QStringLiteral("emblem-information")); 0116 } 0117 0118 QIcon Formatting::questionIcon() 0119 { 0120 return QIcon::fromTheme(QStringLiteral("emblem-question")); 0121 } 0122 0123 QIcon Formatting::unavailableIcon() 0124 { 0125 return QIcon::fromTheme(QStringLiteral("emblem-unavailable")); 0126 } 0127 0128 QIcon Formatting::warningIcon() 0129 { 0130 return QIcon::fromTheme(QStringLiteral("emblem-warning")); 0131 } 0132 0133 QIcon Formatting::errorIcon() 0134 { 0135 return QIcon::fromTheme(QStringLiteral("emblem-error")); 0136 } 0137 0138 // 0139 // Name 0140 // 0141 0142 QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_) 0143 { 0144 if (proto == GpgME::OpenPGP) { 0145 const QString name = QString::fromUtf8(name_); 0146 if (name.isEmpty()) { 0147 return QString(); 0148 } 0149 const QString comment = QString::fromUtf8(comment_); 0150 if (comment.isEmpty()) { 0151 return name; 0152 } 0153 return QStringLiteral("%1 (%2)").arg(name, comment); 0154 } 0155 0156 if (proto == GpgME::CMS) { 0157 const DN subject(id); 0158 const QString cn = subject[QStringLiteral("CN")].trimmed(); 0159 if (cn.isEmpty()) { 0160 return subject.prettyDN(); 0161 } 0162 return cn; 0163 } 0164 0165 return QString(); 0166 } 0167 0168 QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_) 0169 { 0170 return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_)); 0171 } 0172 0173 QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment) 0174 { 0175 if (proto == GpgME::OpenPGP) { 0176 if (name.isEmpty()) { 0177 if (email.isEmpty()) { 0178 return QString(); 0179 } else if (comment.isEmpty()) { 0180 return QStringLiteral("<%1>").arg(email); 0181 } else { 0182 return QStringLiteral("(%2) <%1>").arg(email, comment); 0183 } 0184 } 0185 if (email.isEmpty()) { 0186 if (comment.isEmpty()) { 0187 return name; 0188 } else { 0189 return QStringLiteral("%1 (%2)").arg(name, comment); 0190 } 0191 } 0192 if (comment.isEmpty()) { 0193 return QStringLiteral("%1 <%2>").arg(name, email); 0194 } else { 0195 return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment); 0196 } 0197 } 0198 0199 if (proto == GpgME::CMS) { 0200 const DN subject(id); 0201 const QString cn = subject[QStringLiteral("CN")].trimmed(); 0202 if (cn.isEmpty()) { 0203 return subject.prettyDN(); 0204 } 0205 return cn; 0206 } 0207 return QString(); 0208 } 0209 0210 QString Formatting::prettyUserID(const UserID &uid) 0211 { 0212 if (uid.parent().protocol() == GpgME::OpenPGP) { 0213 return prettyNameAndEMail(uid); 0214 } 0215 const QByteArray id = QByteArray(uid.id()).trimmed(); 0216 if (id.startsWith('<')) { 0217 return prettyEMail(uid.email(), uid.id()); 0218 } 0219 if (id.startsWith('(')) { 0220 // ### parse uri/dns: 0221 return QString::fromUtf8(uid.id()); 0222 } else { 0223 return DN(uid.id()).prettyDN(); 0224 } 0225 } 0226 0227 QString Formatting::prettyKeyID(const char *id) 0228 { 0229 if (!id) { 0230 return QString(); 0231 } 0232 return QLatin1StringView("0x") + QString::fromLatin1(id).toUpper(); 0233 } 0234 0235 QString Formatting::prettyNameAndEMail(const UserID &uid) 0236 { 0237 return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment()); 0238 } 0239 0240 QString Formatting::prettyNameAndEMail(const Key &key) 0241 { 0242 return prettyNameAndEMail(key.userID(0)); 0243 } 0244 0245 QString Formatting::prettyName(const Key &key) 0246 { 0247 return prettyName(key.userID(0)); 0248 } 0249 0250 QString Formatting::prettyName(const UserID &uid) 0251 { 0252 return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment()); 0253 } 0254 0255 QString Formatting::prettyName(const UserID::Signature &sig) 0256 { 0257 return prettyName(GpgME::OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment()); 0258 } 0259 0260 // 0261 // EMail 0262 // 0263 0264 QString Formatting::prettyEMail(const Key &key) 0265 { 0266 for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) { 0267 const QString email = prettyEMail(key.userID(i)); 0268 if (!email.isEmpty()) { 0269 return email; 0270 } 0271 } 0272 return QString(); 0273 } 0274 0275 QString Formatting::prettyEMail(const UserID &uid) 0276 { 0277 return prettyEMail(uid.email(), uid.id()); 0278 } 0279 0280 QString Formatting::prettyEMail(const UserID::Signature &sig) 0281 { 0282 return prettyEMail(sig.signerEmail(), sig.signerUserID()); 0283 } 0284 0285 QString Formatting::prettyEMail(const char *email_, const char *id) 0286 { 0287 QString email; 0288 QString name; 0289 QString comment; 0290 if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_), name, email, comment) == KEmailAddress::AddressOk) { 0291 return email; 0292 } else { 0293 return DN(id)[QStringLiteral("EMAIL")].trimmed(); 0294 } 0295 } 0296 0297 // 0298 // Tooltip 0299 // 0300 0301 namespace 0302 { 0303 0304 static QString protect_whitespace(QString s) 0305 { 0306 static const QLatin1Char SP(' '); 0307 static const QLatin1Char NBSP('\xA0'); 0308 return s.replace(SP, NBSP); 0309 } 0310 0311 template<typename T_arg> 0312 QString format_row(const QString &field, const T_arg &arg) 0313 { 0314 return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg); 0315 } 0316 QString format_row(const QString &field, const QString &arg) 0317 { 0318 return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg.toHtmlEscaped()); 0319 } 0320 QString format_row(const QString &field, const char *arg) 0321 { 0322 return format_row(field, QString::fromUtf8(arg)); 0323 } 0324 0325 QString format_keytype(const Key &key) 0326 { 0327 const Subkey subkey = key.subkey(0); 0328 if (key.hasSecret()) { 0329 return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString())); 0330 } else { 0331 return i18n("%1-bit %2", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString())); 0332 } 0333 } 0334 0335 QString format_subkeytype(const Subkey &subkey) 0336 { 0337 const auto algo = subkey.publicKeyAlgorithm(); 0338 0339 if (algo == Subkey::AlgoECC || algo == Subkey::AlgoECDSA || algo == Subkey::AlgoECDH || algo == Subkey::AlgoEDDSA) { 0340 return QString::fromStdString(subkey.algoName()); 0341 } 0342 return i18n("%1-bit %2", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString())); 0343 } 0344 0345 QString format_keyusage(const Key &key) 0346 { 0347 QStringList capabilities; 0348 if (Kleo::keyHasSign(key)) { 0349 if (key.isQualified()) { 0350 capabilities.push_back(i18n("Signing (Qualified)")); 0351 } else { 0352 capabilities.push_back(i18n("Signing")); 0353 } 0354 } 0355 if (Kleo::keyHasEncrypt(key)) { 0356 capabilities.push_back(i18n("Encryption")); 0357 } 0358 if (Kleo::keyHasCertify(key)) { 0359 capabilities.push_back(i18n("Certifying User-IDs")); 0360 } 0361 if (Kleo::keyHasAuthenticate(key)) { 0362 capabilities.push_back(i18n("SSH Authentication")); 0363 } 0364 return capabilities.join(QLatin1StringView(", ")); 0365 } 0366 0367 QString format_subkeyusage(const Subkey &subkey) 0368 { 0369 QStringList capabilities; 0370 if (subkey.canSign()) { 0371 if (subkey.isQualified()) { 0372 capabilities.push_back(i18n("Signing (Qualified)")); 0373 } else { 0374 capabilities.push_back(i18n("Signing")); 0375 } 0376 } 0377 if (subkey.canEncrypt()) { 0378 capabilities.push_back(i18n("Encryption")); 0379 } 0380 if (subkey.canCertify()) { 0381 capabilities.push_back(i18n("Certifying User-IDs")); 0382 } 0383 if (subkey.canAuthenticate()) { 0384 capabilities.push_back(i18n("SSH Authentication")); 0385 } 0386 return capabilities.join(QLatin1StringView(", ")); 0387 } 0388 0389 static QString time_t2string(time_t t) 0390 { 0391 const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t)); 0392 return QLocale().toString(dt, QLocale::ShortFormat); 0393 } 0394 0395 static QString make_red(const QString &txt) 0396 { 0397 return QLatin1StringView("<font color=\"red\">") + txt.toHtmlEscaped() + QLatin1String("</font>"); 0398 } 0399 0400 } 0401 0402 static QString toolTipInternal(const GpgME::Key &key, const GpgME::UserID &userID, int flags) 0403 { 0404 if (flags == 0 || (key.protocol() != GpgME::CMS && key.protocol() != GpgME::OpenPGP)) { 0405 return QString(); 0406 } 0407 0408 const Subkey subkey = key.subkey(0); 0409 0410 QString result; 0411 if (flags & Formatting::Validity) { 0412 if (key.protocol() == GpgME::OpenPGP || (key.keyListMode() & Validate)) { 0413 if (userID.isRevoked() || key.isRevoked()) { 0414 result = make_red(i18n("Revoked")); 0415 } else if (key.isExpired()) { 0416 result = make_red(i18n("Expired")); 0417 } else if (key.isDisabled()) { 0418 result = i18n("Disabled"); 0419 } else if (key.keyListMode() & GpgME::Validate) { 0420 if (!userID.isNull()) { 0421 if (userID.validity() >= UserID::Validity::Full) { 0422 result = i18n("User-ID is certified."); 0423 const auto compliance = Formatting::complianceStringForUserID(userID); 0424 if (!compliance.isEmpty()) { 0425 result += QStringLiteral("<br>") + compliance; 0426 } 0427 } else { 0428 result = i18n("User-ID is not certified."); 0429 } 0430 } else { 0431 unsigned int fullyTrusted = 0; 0432 for (const auto &uid : key.userIDs()) { 0433 if (uid.validity() >= UserID::Validity::Full) { 0434 fullyTrusted++; 0435 } 0436 } 0437 if (fullyTrusted == key.numUserIDs()) { 0438 result = i18n("All User-IDs are certified."); 0439 const auto compliance = Formatting::complianceStringForKey(key); 0440 if (!compliance.isEmpty()) { 0441 result += QStringLiteral("<br>") + compliance; 0442 } 0443 } else { 0444 result = i18np("One User-ID is not certified.", "%1 User-IDs are not certified.", key.numUserIDs() - fullyTrusted); 0445 } 0446 } 0447 } else { 0448 result = i18n("The validity cannot be checked at the moment."); 0449 } 0450 } else { 0451 result = i18n("The validity cannot be checked at the moment."); 0452 } 0453 } 0454 if (flags == Formatting::Validity) { 0455 return result; 0456 } 0457 0458 result += QLatin1StringView("<table border=\"0\">"); 0459 if (key.protocol() == GpgME::CMS) { 0460 if (flags & Formatting::SerialNumber) { 0461 result += format_row(i18n("Serial number"), key.issuerSerial()); 0462 } 0463 if (flags & Formatting::Issuer) { 0464 result += format_row(i18n("Issuer"), key.issuerName()); 0465 } 0466 } 0467 if (flags & Formatting::UserIDs) { 0468 if (userID.isNull()) { 0469 const std::vector<UserID> uids = key.userIDs(); 0470 if (!uids.empty()) { 0471 result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User-ID"), Formatting::prettyUserID(uids.front())); 0472 } 0473 if (uids.size() > 1) { 0474 for (auto it = uids.begin() + 1, end = uids.end(); it != end; ++it) { 0475 if (!it->isRevoked() && !it->isInvalid()) { 0476 result += format_row(i18n("a.k.a."), Formatting::prettyUserID(*it)); 0477 } 0478 } 0479 } 0480 } else { 0481 result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User-ID"), Formatting::prettyUserID(userID)); 0482 } 0483 } 0484 if (flags & Formatting::ExpiryDates) { 0485 result += format_row(i18n("Valid from"), time_t2string(subkey.creationTime())); 0486 0487 if (!subkey.neverExpires()) { 0488 result += format_row(i18n("Valid until"), time_t2string(subkey.expirationTime())); 0489 } 0490 } 0491 0492 if (flags & Formatting::CertificateType) { 0493 result += format_row(i18n("Type"), format_keytype(key)); 0494 } 0495 if (flags & Formatting::CertificateUsage) { 0496 result += format_row(i18n("Usage"), format_keyusage(key)); 0497 } 0498 if (flags & Formatting::KeyID) { 0499 result += format_row(i18n("Key-ID"), QString::fromLatin1(key.shortKeyID())); 0500 } 0501 if (flags & Formatting::Fingerprint) { 0502 result += format_row(i18n("Fingerprint"), key.primaryFingerprint()); 0503 } 0504 if (flags & Formatting::OwnerTrust) { 0505 if (key.protocol() == GpgME::OpenPGP) { 0506 result += format_row(i18n("Certification trust"), Formatting::ownerTrustShort(key)); 0507 } else if (key.isRoot()) { 0508 result += format_row(i18n("Trusted issuer?"), (userID.isNull() ? key.userID(0) : userID).validity() == UserID::Ultimate ? i18n("Yes") : i18n("No")); 0509 } 0510 } 0511 if (flags & Formatting::StorageLocation) { 0512 if (const char *card = subkey.cardSerialNumber()) { 0513 result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); 0514 } else { 0515 result += format_row(i18n("Stored"), i18nc("stored...", "on this computer")); 0516 } 0517 } 0518 if (flags & Formatting::Subkeys) { 0519 for (const auto &sub : key.subkeys()) { 0520 result += QLatin1StringView("<hr/>"); 0521 result += format_row(i18n("Subkey"), sub.fingerprint()); 0522 if (sub.isRevoked()) { 0523 result += format_row(i18n("Status"), i18n("Revoked")); 0524 } else if (sub.isExpired()) { 0525 result += format_row(i18n("Status"), i18n("Expired")); 0526 } 0527 if (flags & Formatting::ExpiryDates) { 0528 result += format_row(i18n("Valid from"), time_t2string(sub.creationTime())); 0529 0530 if (!sub.neverExpires()) { 0531 result += format_row(i18n("Valid until"), time_t2string(sub.expirationTime())); 0532 } 0533 } 0534 0535 if (flags & Formatting::CertificateType) { 0536 result += format_row(i18n("Type"), format_subkeytype(sub)); 0537 } 0538 if (flags & Formatting::CertificateUsage) { 0539 result += format_row(i18n("Usage"), format_subkeyusage(sub)); 0540 } 0541 if (flags & Formatting::StorageLocation) { 0542 if (const char *card = sub.cardSerialNumber()) { 0543 result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); 0544 } else { 0545 result += format_row(i18n("Stored"), i18nc("stored...", "on this computer")); 0546 } 0547 } 0548 } 0549 } 0550 result += QLatin1StringView("</table>"); 0551 0552 return result; 0553 } 0554 0555 QString Formatting::toolTip(const Key &key, int flags) 0556 { 0557 return toolTipInternal(key, UserID(), flags); 0558 } 0559 0560 namespace 0561 { 0562 template<typename Container> 0563 QString getValidityStatement(const Container &keys) 0564 { 0565 const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { 0566 return key.protocol() == GpgME::OpenPGP; 0567 }); 0568 const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { 0569 return key.keyListMode() & Validate; 0570 }); 0571 if (allKeysAreOpenPGP || allKeysAreValidated) { 0572 const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad)); 0573 if (someKeysAreBad) { 0574 return i18n("Some keys are revoked, expired, disabled, or invalid."); 0575 } else { 0576 const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity); 0577 if (allKeysAreFullyValid) { 0578 return i18n("All keys are certified."); 0579 } else { 0580 return i18n("Some keys are not certified."); 0581 } 0582 } 0583 } 0584 return i18n("The validity of the keys cannot be checked at the moment."); 0585 } 0586 } 0587 0588 QString Formatting::toolTip(const KeyGroup &group, int flags) 0589 { 0590 static const unsigned int maxNumKeysForTooltip = 20; 0591 0592 if (group.isNull()) { 0593 return QString(); 0594 } 0595 0596 const KeyGroup::Keys &keys = group.keys(); 0597 if (keys.size() == 0) { 0598 return i18nc("@info:tooltip", "This group does not contain any keys."); 0599 } 0600 0601 const QString validity = (flags & Validity) ? getValidityStatement(keys) : QString(); 0602 if (flags == Validity) { 0603 return validity; 0604 } 0605 0606 // list either up to maxNumKeysForTooltip keys or (maxNumKeysForTooltip-1) keys followed by "and n more keys" 0607 const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size(); 0608 0609 QStringList result; 0610 result.reserve(3 + 2 + numKeysForTooltip + 2); 0611 if (!validity.isEmpty()) { 0612 result.push_back(QStringLiteral("<p>")); 0613 result.push_back(validity.toHtmlEscaped()); 0614 result.push_back(QStringLiteral("</p>")); 0615 } 0616 0617 result.push_back(QStringLiteral("<p>")); 0618 result.push_back(i18n("Keys:")); 0619 { 0620 auto it = keys.cbegin(); 0621 for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) { 0622 result.push_back(QLatin1StringView("<br>") + Formatting::summaryLine(*it).toHtmlEscaped()); 0623 } 0624 } 0625 if (keys.size() > numKeysForTooltip) { 0626 result.push_back(QLatin1StringView("<br>") 0627 + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip)); 0628 } 0629 result.push_back(QStringLiteral("</p>")); 0630 0631 return result.join(QLatin1Char('\n')); 0632 } 0633 0634 QString Formatting::toolTip(const UserID &userID, int flags) 0635 { 0636 return toolTipInternal(userID.parent(), userID, flags); 0637 } 0638 0639 // 0640 // Creation and Expiration 0641 // 0642 0643 namespace 0644 { 0645 static QDate time_t2date(time_t t) 0646 { 0647 if (!t) { 0648 return {}; 0649 } 0650 const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t)); 0651 return dt.date(); 0652 } 0653 static QString accessible_date_format() 0654 { 0655 return i18nc( 0656 "date format suitable for screen readers; " 0657 "d: day as a number without a leading zero, " 0658 "MMMM: localized month name, " 0659 "yyyy: year as a four digit number", 0660 "MMMM d, yyyy"); 0661 } 0662 0663 template<typename T> 0664 QString expiration_date_string(const T &tee, const QString &noExpiration) 0665 { 0666 return tee.neverExpires() ? noExpiration : Formatting::dateString(time_t2date(tee.expirationTime())); 0667 } 0668 template<typename T> 0669 QDate creation_date(const T &tee) 0670 { 0671 return time_t2date(tee.creationTime()); 0672 } 0673 template<typename T> 0674 QDate expiration_date(const T &tee) 0675 { 0676 return time_t2date(tee.expirationTime()); 0677 } 0678 } 0679 0680 QString Formatting::dateString(time_t t) 0681 { 0682 return dateString(time_t2date(t)); 0683 } 0684 0685 QString Formatting::dateString(const QDate &date) 0686 { 0687 return QLocale().toString(date, QLocale::ShortFormat); 0688 } 0689 0690 QString Formatting::accessibleDate(time_t t) 0691 { 0692 return accessibleDate(time_t2date(t)); 0693 } 0694 0695 QString Formatting::accessibleDate(const QDate &date) 0696 { 0697 return QLocale().toString(date, accessible_date_format()); 0698 } 0699 0700 QString Formatting::expirationDateString(const Key &key, const QString &noExpiration) 0701 { 0702 // if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD), 0703 // then we assume that the date is valid; if the date is zero for a remote key, then 0704 // we don't know if it's unknown or unlimited 0705 return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) // 0706 ? i18nc("@info the expiration date of the key is unknown", "unknown") 0707 : expiration_date_string(key.subkey(0), noExpiration); 0708 } 0709 0710 QString Formatting::expirationDateString(const Subkey &subkey, const QString &noExpiration) 0711 { 0712 return expiration_date_string(subkey, noExpiration); 0713 } 0714 0715 QString Formatting::expirationDateString(const UserID::Signature &sig, const QString &noExpiration) 0716 { 0717 return expiration_date_string(sig, noExpiration); 0718 } 0719 0720 QDate Formatting::expirationDate(const Key &key) 0721 { 0722 return expiration_date(key.subkey(0)); 0723 } 0724 0725 QDate Formatting::expirationDate(const Subkey &subkey) 0726 { 0727 return expiration_date(subkey); 0728 } 0729 0730 QDate Formatting::expirationDate(const UserID::Signature &sig) 0731 { 0732 return expiration_date(sig); 0733 } 0734 0735 QString Formatting::accessibleExpirationDate(const Key &key, const QString &noExpiration) 0736 { 0737 // if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD), 0738 // then we assume that the date is valid; if the date is zero for a remote key, then 0739 // we don't know if it's unknown or unlimited 0740 return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) // 0741 ? i18nc("@info the expiration date of the key is unknown", "unknown") 0742 : accessibleExpirationDate(key.subkey(0), noExpiration); 0743 } 0744 0745 QString Formatting::accessibleExpirationDate(const Subkey &subkey, const QString &noExpiration) 0746 { 0747 if (subkey.neverExpires()) { 0748 return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration; 0749 } else { 0750 return accessibleDate(expirationDate(subkey)); 0751 } 0752 } 0753 0754 QString Formatting::accessibleExpirationDate(const UserID::Signature &sig, const QString &noExpiration) 0755 { 0756 if (sig.neverExpires()) { 0757 return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration; 0758 } else { 0759 return accessibleDate(expirationDate(sig)); 0760 } 0761 } 0762 0763 QString Formatting::creationDateString(const Key &key) 0764 { 0765 return dateString(creation_date(key.subkey(0))); 0766 } 0767 0768 QString Formatting::creationDateString(const Subkey &subkey) 0769 { 0770 return dateString(creation_date(subkey)); 0771 } 0772 0773 QString Formatting::creationDateString(const UserID::Signature &sig) 0774 { 0775 return dateString(creation_date(sig)); 0776 } 0777 0778 QDate Formatting::creationDate(const Key &key) 0779 { 0780 return creation_date(key.subkey(0)); 0781 } 0782 0783 QDate Formatting::creationDate(const Subkey &subkey) 0784 { 0785 return creation_date(subkey); 0786 } 0787 0788 QDate Formatting::creationDate(const UserID::Signature &sig) 0789 { 0790 return creation_date(sig); 0791 } 0792 0793 QString Formatting::accessibleCreationDate(const Key &key) 0794 { 0795 return accessibleDate(creationDate(key)); 0796 } 0797 0798 QString Formatting::accessibleCreationDate(const Subkey &subkey) 0799 { 0800 return accessibleDate(creationDate(subkey)); 0801 } 0802 0803 // 0804 // Types 0805 // 0806 0807 QString Formatting::displayName(GpgME::Protocol p) 0808 { 0809 if (p == GpgME::CMS) { 0810 return i18nc("X.509/CMS encryption standard", "S/MIME"); 0811 } 0812 if (p == GpgME::OpenPGP) { 0813 return i18n("OpenPGP"); 0814 } 0815 return i18nc("Unknown encryption protocol", "Unknown"); 0816 } 0817 0818 QString Formatting::type(const Key &key) 0819 { 0820 return displayName(key.protocol()); 0821 } 0822 0823 QString Formatting::type(const Subkey &subkey) 0824 { 0825 return QString::fromUtf8(subkey.publicKeyAlgorithmAsString()); 0826 } 0827 0828 QString Formatting::type(const KeyGroup &group) 0829 { 0830 Q_UNUSED(group) 0831 return i18nc("a group of keys/certificates", "Group"); 0832 } 0833 0834 // 0835 // Status / Validity 0836 // 0837 0838 QString Formatting::ownerTrustShort(const Key &key) 0839 { 0840 return ownerTrustShort(key.ownerTrust()); 0841 } 0842 0843 QString Formatting::ownerTrustShort(Key::OwnerTrust trust) 0844 { 0845 switch (trust) { 0846 case Key::Unknown: 0847 return i18nc("unknown trust level", "unknown"); 0848 case Key::Never: 0849 return i18n("untrusted"); 0850 case Key::Marginal: 0851 return i18nc("marginal trust", "marginal"); 0852 case Key::Full: 0853 return i18nc("full trust", "full"); 0854 case Key::Ultimate: 0855 return i18nc("ultimate trust", "ultimate"); 0856 case Key::Undefined: 0857 return i18nc("undefined trust", "undefined"); 0858 default: 0859 Q_ASSERT(!"unexpected owner trust value"); 0860 break; 0861 } 0862 return QString(); 0863 } 0864 0865 QString Formatting::validityShort(const Subkey &subkey) 0866 { 0867 if (subkey.isRevoked()) { 0868 return i18n("revoked"); 0869 } 0870 if (subkey.isExpired()) { 0871 return i18n("expired"); 0872 } 0873 if (subkey.isDisabled()) { 0874 return i18n("disabled"); 0875 } 0876 if (subkey.isInvalid()) { 0877 return i18n("invalid"); 0878 } 0879 return i18nc("as in good/valid signature", "good"); 0880 } 0881 0882 QString Formatting::validityShort(const UserID &uid) 0883 { 0884 if (uid.isRevoked()) { 0885 return i18n("revoked"); 0886 } 0887 if (uid.isInvalid()) { 0888 return i18n("invalid"); 0889 } 0890 switch (uid.validity()) { 0891 case UserID::Unknown: 0892 return i18nc("unknown trust level", "unknown"); 0893 case UserID::Undefined: 0894 return i18nc("undefined trust", "undefined"); 0895 case UserID::Never: 0896 return i18n("untrusted"); 0897 case UserID::Marginal: 0898 return i18nc("marginal trust", "marginal"); 0899 case UserID::Full: 0900 return i18nc("full trust", "full"); 0901 case UserID::Ultimate: 0902 return i18nc("ultimate trust", "ultimate"); 0903 } 0904 return QString(); 0905 } 0906 0907 QString Formatting::validityShort(const UserID::Signature &sig) 0908 { 0909 switch (sig.status()) { 0910 case UserID::Signature::NoError: 0911 if (!sig.isInvalid()) { 0912 /* See RFC 4880 Section 5.2.1 */ 0913 switch (sig.certClass()) { 0914 case 0x10: /* Generic */ 0915 case 0x11: /* Persona */ 0916 case 0x12: /* Casual */ 0917 case 0x13: /* Positive */ 0918 return i18n("valid"); 0919 case 0x30: 0920 return i18n("revoked"); 0921 default: 0922 return i18n("class %1", sig.certClass()); 0923 } 0924 } 0925 [[fallthrough]]; 0926 // fall through: 0927 case UserID::Signature::GeneralError: 0928 return i18n("invalid"); 0929 case UserID::Signature::SigExpired: 0930 return i18n("expired"); 0931 case UserID::Signature::KeyExpired: 0932 return i18n("certificate expired"); 0933 case UserID::Signature::BadSignature: 0934 return i18nc("fake/invalid signature", "bad"); 0935 case UserID::Signature::NoPublicKey: { 0936 /* GnuPG returns the same error for no public key as for expired 0937 * or revoked certificates. */ 0938 const auto key = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID()); 0939 if (key.isNull()) { 0940 return i18n("no public key"); 0941 } else if (key.isExpired()) { 0942 return i18n("key expired"); 0943 } else if (key.isRevoked()) { 0944 return i18n("key revoked"); 0945 } else if (key.isDisabled()) { 0946 return i18n("key disabled"); 0947 } 0948 /* can't happen */ 0949 return QStringLiteral("unknown"); 0950 } 0951 } 0952 return QString(); 0953 } 0954 0955 QIcon Formatting::validityIcon(const UserID::Signature &sig) 0956 { 0957 switch (sig.status()) { 0958 case UserID::Signature::NoError: 0959 if (!sig.isInvalid()) { 0960 /* See RFC 4880 Section 5.2.1 */ 0961 switch (sig.certClass()) { 0962 case 0x10: /* Generic */ 0963 case 0x11: /* Persona */ 0964 case 0x12: /* Casual */ 0965 case 0x13: /* Positive */ 0966 return Formatting::successIcon(); 0967 case 0x30: 0968 return Formatting::errorIcon(); 0969 default: 0970 return QIcon(); 0971 } 0972 } 0973 [[fallthrough]]; 0974 // fall through: 0975 case UserID::Signature::BadSignature: 0976 case UserID::Signature::GeneralError: 0977 return Formatting::errorIcon(); 0978 case UserID::Signature::SigExpired: 0979 case UserID::Signature::KeyExpired: 0980 return Formatting::infoIcon(); 0981 case UserID::Signature::NoPublicKey: 0982 return Formatting::questionIcon(); 0983 } 0984 return QIcon(); 0985 } 0986 0987 QString Formatting::formatKeyLink(const Key &key) 0988 { 0989 if (key.isNull()) { 0990 return QString(); 0991 } 0992 return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(QLatin1StringView(key.primaryFingerprint()), Formatting::prettyName(key)); 0993 } 0994 0995 QString Formatting::formatForComboBox(const GpgME::Key &key) 0996 { 0997 const QString name = prettyName(key); 0998 QString mail = prettyEMail(key); 0999 if (!mail.isEmpty()) { 1000 mail = QLatin1Char('<') + mail + QLatin1Char('>'); 1001 } 1002 return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1StringView(key.shortKeyID())).simplified(); 1003 } 1004 1005 QString Formatting::nameAndEmailForSummaryLine(const UserID &id) 1006 { 1007 Q_ASSERT(!id.isNull()); 1008 1009 const QString email = Formatting::prettyEMail(id); 1010 const QString name = Formatting::prettyName(id); 1011 1012 if (name.isEmpty()) { 1013 return email; 1014 } else if (email.isEmpty()) { 1015 return name; 1016 } else { 1017 return QStringLiteral("%1 <%2>").arg(name, email); 1018 } 1019 } 1020 1021 QString Formatting::nameAndEmailForSummaryLine(const Key &key) 1022 { 1023 Q_ASSERT(!key.isNull()); 1024 1025 const QString email = Formatting::prettyEMail(key); 1026 const QString name = Formatting::prettyName(key); 1027 1028 if (name.isEmpty()) { 1029 return email; 1030 } else if (email.isEmpty()) { 1031 return name; 1032 } else { 1033 return QStringLiteral("%1 <%2>").arg(name, email); 1034 } 1035 } 1036 1037 const char *Formatting::summaryToString(const Signature::Summary summary) 1038 { 1039 if (summary & Signature::Red) { 1040 return "RED"; 1041 } 1042 if (summary & Signature::Green) { 1043 return "GREEN"; 1044 } 1045 return "YELLOW"; 1046 } 1047 1048 QString Formatting::signatureToString(const Signature &sig, const Key &key) 1049 { 1050 if (sig.isNull()) { 1051 return QString(); 1052 } 1053 1054 const bool red = (sig.summary() & Signature::Red); 1055 const bool valid = (sig.summary() & Signature::Valid); 1056 1057 if (red) { 1058 if (key.isNull()) { 1059 if (const char *fpr = sig.fingerprint()) { 1060 return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), Formatting::errorAsString(sig.status())); 1061 } else { 1062 return i18n("Bad signature by an unknown certificate: %1", Formatting::errorAsString(sig.status())); 1063 } 1064 } else { 1065 return i18n("Bad signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status())); 1066 } 1067 1068 } else if (valid) { 1069 if (key.isNull()) { 1070 if (const char *fpr = sig.fingerprint()) { 1071 return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr)); 1072 } else { 1073 return i18n("Good signature by an unknown certificate."); 1074 } 1075 } else { 1076 return i18n("Good signature by %1.", nameAndEmailForSummaryLine(key)); 1077 } 1078 1079 } else if (key.isNull()) { 1080 if (const char *fpr = sig.fingerprint()) { 1081 return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), Formatting::errorAsString(sig.status())); 1082 } else { 1083 return i18n("Invalid signature by an unknown certificate: %1", Formatting::errorAsString(sig.status())); 1084 } 1085 } else { 1086 return i18n("Invalid signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status())); 1087 } 1088 } 1089 1090 // 1091 // ImportResult 1092 // 1093 1094 QString Formatting::importMetaData(const Import &import, const QStringList &ids) 1095 { 1096 const QString result = importMetaData(import); 1097 if (result.isEmpty()) { 1098 return QString(); 1099 } else { 1100 return result + QLatin1Char('\n') + i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') + ids.join(QLatin1Char('\n')); 1101 } 1102 } 1103 1104 QString Formatting::importMetaData(const Import &import) 1105 { 1106 if (import.isNull()) { 1107 return QString(); 1108 } 1109 1110 if (import.error().isCanceled()) { 1111 return i18n("The import of this certificate was canceled."); 1112 } 1113 if (import.error()) { 1114 return i18n("An error occurred importing this certificate: %1", Formatting::errorAsString(import.error())); 1115 } 1116 1117 const unsigned int status = import.status(); 1118 if (status & Import::NewKey) { 1119 return (status & Import::ContainedSecretKey) ? i18n("This certificate was new to your keystore. The secret key is available.") 1120 : i18n("This certificate is new to your keystore."); 1121 } 1122 1123 QStringList results; 1124 if (status & Import::NewUserIDs) { 1125 results.push_back(i18n("New user-ids were added to this certificate by the import.")); 1126 } 1127 if (status & Import::NewSignatures) { 1128 results.push_back(i18n("New signatures were added to this certificate by the import.")); 1129 } 1130 if (status & Import::NewSubkeys) { 1131 results.push_back(i18n("New subkeys were added to this certificate by the import.")); 1132 } 1133 1134 return results.empty() ? i18n("The import contained no new data for this certificate. It is unchanged.") : results.join(QLatin1Char('\n')); 1135 } 1136 1137 // 1138 // Overview in CertificateDetailsDialog 1139 // 1140 1141 QString Formatting::formatOverview(const Key &key) 1142 { 1143 return toolTip(key, AllOptions); 1144 } 1145 1146 QString Formatting::usageString(const Subkey &sub) 1147 { 1148 QStringList usageStrings; 1149 if (sub.canCertify()) { 1150 usageStrings << i18n("Certify"); 1151 } 1152 if (sub.canSign()) { 1153 usageStrings << i18n("Sign"); 1154 } 1155 if (sub.canEncrypt()) { 1156 usageStrings << i18n("Encrypt"); 1157 } 1158 if (sub.canAuthenticate()) { 1159 usageStrings << i18n("Authenticate"); 1160 } 1161 if (sub.canRenc()) { 1162 usageStrings << i18nc("Means 'Additional Decryption Subkey'; Don't try translating that, though.", "ADSK"); 1163 } 1164 return usageStrings.join(QLatin1StringView(", ")); 1165 } 1166 1167 QString Formatting::summaryLine(const UserID &id) 1168 { 1169 return i18nc("name <email> (validity, protocol, creation date)", 1170 "%1 (%2, %3, created: %4)", 1171 nameAndEmailForSummaryLine(id), 1172 Formatting::complianceStringShort(id), 1173 displayName(id.parent().protocol()), 1174 Formatting::creationDateString(id.parent())); 1175 } 1176 1177 QString Formatting::summaryLine(const Key &key) 1178 { 1179 return nameAndEmailForSummaryLine(key) + QLatin1Char(' ') 1180 + i18nc("(validity, protocol, creation date)", 1181 "(%1, %2, created: %3)", 1182 Formatting::complianceStringShort(key), 1183 displayName(key.protocol()), 1184 Formatting::creationDateString(key)); 1185 } 1186 1187 QString Formatting::summaryLine(const KeyGroup &group) 1188 { 1189 switch (group.source()) { 1190 case KeyGroup::ApplicationConfig: 1191 case KeyGroup::GnuPGConfig: 1192 return i18ncp("name of group of keys (n key(s), validity)", 1193 "%2 (1 key, %3)", 1194 "%2 (%1 keys, %3)", 1195 group.keys().size(), 1196 group.name(), 1197 Formatting::complianceStringShort(group)); 1198 case KeyGroup::Tags: 1199 return i18ncp("name of group of keys (n key(s), validity, tag)", 1200 "%2 (1 key, %3, tag)", 1201 "%2 (%1 keys, %3, tag)", 1202 group.keys().size(), 1203 group.name(), 1204 Formatting::complianceStringShort(group)); 1205 default: 1206 return i18ncp("name of group of keys (n key(s), validity, group ...)", 1207 "%2 (1 key, %3, unknown origin)", 1208 "%2 (%1 keys, %3, unknown origin)", 1209 group.keys().size(), 1210 group.name(), 1211 Formatting::complianceStringShort(group)); 1212 } 1213 } 1214 1215 // Icon for certificate selection indication 1216 QIcon Formatting::iconForUid(const UserID &uid) 1217 { 1218 if (Kleo::isRevokedOrExpired(uid)) { 1219 return Formatting::errorIcon(); 1220 } 1221 return iconForValidity(uid); 1222 } 1223 1224 QString Formatting::validity(const UserID &uid) 1225 { 1226 switch (uid.validity()) { 1227 case UserID::Ultimate: 1228 return i18n("The certificate is marked as your own."); 1229 case UserID::Full: 1230 return i18n("The certificate belongs to this recipient."); 1231 case UserID::Marginal: 1232 return i18n("The trust model indicates marginally that the certificate belongs to this recipient."); 1233 case UserID::Never: 1234 return i18n("This certificate should not be used."); 1235 case UserID::Undefined: 1236 case UserID::Unknown: 1237 default: 1238 return i18n("There is no indication that this certificate belongs to this recipient."); 1239 } 1240 } 1241 1242 QString Formatting::validity(const KeyGroup &group) 1243 { 1244 if (group.isNull()) { 1245 return QString(); 1246 } 1247 1248 const KeyGroup::Keys &keys = group.keys(); 1249 if (keys.size() == 0) { 1250 return i18n("This group does not contain any keys."); 1251 } 1252 1253 return getValidityStatement(keys); 1254 } 1255 1256 namespace 1257 { 1258 template<typename Container> 1259 UserID::Validity minimalValidity(const Container &keys) 1260 { 1261 const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [](int validity, const Key &key) { 1262 return std::min<int>(validity, minimalValidityOfNotRevokedUserIDs(key)); 1263 }); 1264 return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown; 1265 } 1266 1267 template<typename Container> 1268 bool allKeysAreCompliant(const Container &keys) 1269 { 1270 if (!DeVSCompliance::isActive()) { 1271 return true; 1272 } 1273 if (!DeVSCompliance::isCompliant()) { 1274 return false; 1275 } 1276 return Kleo::all_of(keys, DeVSCompliance::keyIsCompliant); 1277 } 1278 } 1279 1280 QIcon Formatting::validityIcon(const KeyGroup &group) 1281 { 1282 if (Kleo::any_of(group.keys(), std::mem_fn(&Key::isBad))) { 1283 return Formatting::errorIcon(); 1284 } 1285 return iconForValidityAndCompliance(minimalValidity(group.keys()), allKeysAreCompliant(group.keys())); 1286 } 1287 1288 bool Formatting::uidsHaveFullValidity(const Key &key) 1289 { 1290 return allUserIDsHaveFullValidity(key); 1291 } 1292 1293 QString Formatting::complianceMode() 1294 { 1295 const auto complianceValue = getCryptoConfigStringValue("gpg", "compliance"); 1296 return complianceValue == QLatin1StringView("gnupg") ? QString() : complianceValue; 1297 } 1298 1299 bool Formatting::isKeyDeVs(const GpgME::Key &key) 1300 { 1301 return DeVSCompliance::allSubkeysAreCompliant(key); 1302 } 1303 1304 QString Formatting::complianceStringForKey(const GpgME::Key &key) 1305 { 1306 // There will likely be more in the future for other institutions 1307 // for now we only have DE-VS 1308 if (DeVSCompliance::isCompliant()) { 1309 return isRemoteKey(key) // 1310 ? i18nc("@info the compliance of the key with certain requirements is unknown", "unknown") 1311 : DeVSCompliance::name(DeVSCompliance::keyIsCompliant(key)); 1312 } 1313 return QString(); 1314 } 1315 1316 QString Formatting::complianceStringForUserID(const GpgME::UserID &userID) 1317 { 1318 // There will likely be more in the future for other institutions 1319 // for now we only have DE-VS 1320 if (DeVSCompliance::isCompliant()) { 1321 return isRemoteKey(userID.parent()) // 1322 ? i18nc("@info the compliance of the key with certain requirements is unknown", "unknown") 1323 : DeVSCompliance::name(DeVSCompliance::userIDIsCompliant(userID)); 1324 } 1325 return QString(); 1326 } 1327 1328 QString Formatting::complianceStringShort(const GpgME::UserID &id) 1329 { 1330 if (DeVSCompliance::isCompliant() && DeVSCompliance::userIDIsCompliant(id)) { 1331 return QStringLiteral("★ ") + DeVSCompliance::name(true); 1332 } 1333 const bool keyValidityChecked = (id.parent().keyListMode() & GpgME::Validate); 1334 if (keyValidityChecked && id.validity() >= UserID::Full) { 1335 return i18nc("As in 'this user ID is valid.'", "certified"); 1336 } 1337 if (id.parent().isExpired() || isExpired(id)) { 1338 return i18n("expired"); 1339 } 1340 if (id.parent().isRevoked() || id.isRevoked()) { 1341 return i18n("revoked"); 1342 } 1343 if (id.parent().isDisabled()) { 1344 return i18n("disabled"); 1345 } 1346 if (id.parent().isInvalid() || id.isInvalid()) { 1347 return i18n("invalid"); 1348 } 1349 if (keyValidityChecked) { 1350 return i18nc("As in 'this user ID is not certified'", "not certified"); 1351 } 1352 1353 return i18nc("The validity of this user ID has not been/could not be checked", "not checked"); 1354 } 1355 1356 QString Formatting::complianceStringShort(const GpgME::Key &key) 1357 { 1358 if (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(key)) { 1359 return QStringLiteral("★ ") + DeVSCompliance::name(true); 1360 } 1361 const bool keyValidityChecked = (key.keyListMode() & GpgME::Validate); 1362 if (keyValidityChecked && Kleo::allUserIDsHaveFullValidity(key)) { 1363 return i18nc("As in all user IDs are valid.", "certified"); 1364 } 1365 if (key.isExpired()) { 1366 return i18n("expired"); 1367 } 1368 if (key.isRevoked()) { 1369 return i18n("revoked"); 1370 } 1371 if (key.isDisabled()) { 1372 return i18n("disabled"); 1373 } 1374 if (key.isInvalid()) { 1375 return i18n("invalid"); 1376 } 1377 if (keyValidityChecked) { 1378 return i18nc("As in not all user IDs are valid.", "not certified"); 1379 } 1380 1381 return i18nc("The validity of the user IDs has not been/could not be checked", "not checked"); 1382 } 1383 1384 QString Formatting::complianceStringShort(const KeyGroup &group) 1385 { 1386 const KeyGroup::Keys &keys = group.keys(); 1387 1388 const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity); 1389 if (allKeysFullyValid) { 1390 return i18nc("As in all keys are valid.", "all certified"); 1391 } 1392 1393 return i18nc("As in not all keys are valid.", "not all certified"); 1394 } 1395 1396 QString Formatting::prettyID(const char *id) 1397 { 1398 if (!id) { 1399 return QString(); 1400 } 1401 QString ret = QString::fromLatin1(id).toUpper().replace(QRegularExpression(QStringLiteral("(....)")), QStringLiteral("\\1 ")).trimmed(); 1402 // For the standard 10 group fingerprint let us use a double space in the 1403 // middle to increase readability 1404 if (ret.size() == 49) { 1405 ret.insert(24, QLatin1Char(' ')); 1406 } 1407 return ret; 1408 } 1409 1410 QString Formatting::accessibleHexID(const char *id) 1411 { 1412 static const QRegularExpression groupOfFourRegExp{QStringLiteral("(?:(.)(.)(.)(.))")}; 1413 1414 QString ret; 1415 ret = QString::fromLatin1(id); 1416 if (!ret.isEmpty() && (ret.size() % 4 == 0)) { 1417 ret = ret.replace(groupOfFourRegExp, QStringLiteral("\\1 \\2 \\3 \\4, ")).chopped(2); 1418 } 1419 return ret; 1420 } 1421 1422 QString Formatting::origin(int o) 1423 { 1424 switch (o) { 1425 case Key::OriginKS: 1426 return i18n("Keyserver"); 1427 case Key::OriginDane: 1428 return QStringLiteral("DANE"); 1429 case Key::OriginWKD: 1430 return QStringLiteral("WKD"); 1431 case Key::OriginURL: 1432 return QStringLiteral("URL"); 1433 case Key::OriginFile: 1434 return i18n("File import"); 1435 case Key::OriginSelf: 1436 return i18n("Generated"); 1437 case Key::OriginOther: 1438 case Key::OriginUnknown: 1439 default: 1440 return i18n("Unknown"); 1441 } 1442 } 1443 1444 QString Formatting::deVsString(bool compliant) 1445 { 1446 return DeVSCompliance::name(compliant); 1447 } 1448 1449 namespace 1450 { 1451 QString formatTrustScope(const char *trustScope) 1452 { 1453 static const QRegularExpression escapedNonAlphaNum{QStringLiteral(R"(\\([^0-9A-Za-z]))")}; 1454 1455 const auto scopeRegExp = QString::fromUtf8(trustScope); 1456 if (scopeRegExp.startsWith(u"<[^>]+[@.]") && scopeRegExp.endsWith(u">$")) { 1457 // looks like a trust scope regular expression created by gpg 1458 auto domain = scopeRegExp.mid(10, scopeRegExp.size() - 10 - 2); 1459 domain.replace(escapedNonAlphaNum, QStringLiteral(R"(\1)")); 1460 return domain; 1461 } 1462 return scopeRegExp; 1463 } 1464 } 1465 1466 QString Formatting::trustSignatureDomain(const GpgME::UserID::Signature &sig) 1467 { 1468 return formatTrustScope(sig.trustScope()); 1469 } 1470 1471 QString Formatting::trustSignature(const GpgME::UserID::Signature &sig) 1472 { 1473 switch (sig.trustValue()) { 1474 case TrustSignatureTrust::Partial: 1475 return i18nc("Certifies this key as partially trusted introducer for 'domain name'.", 1476 "Certifies this key as partially trusted introducer for '%1'.", 1477 trustSignatureDomain(sig)); 1478 case TrustSignatureTrust::Complete: 1479 return i18nc("Certifies this key as fully trusted introducer for 'domain name'.", 1480 "Certifies this key as fully trusted introducer for '%1'.", 1481 trustSignatureDomain(sig)); 1482 default: 1483 return {}; 1484 } 1485 } 1486 1487 QString Formatting::errorAsString(const GpgME::Error &error) 1488 { 1489 #ifdef Q_OS_WIN 1490 // On Windows, we set GpgME resp. libgpg-error to return (translated) error messages as UTF-8 1491 const char *s = error.asString(); 1492 qCDebug(LIBKLEO_LOG) << __func__ << "gettext_use_utf8(-1) returns" << gettext_use_utf8(-1); 1493 qCDebug(LIBKLEO_LOG) << __func__ << "error:" << s; 1494 qCDebug(LIBKLEO_LOG) << __func__ << "error (percent-encoded):" << QByteArray{s}.toPercentEncoding(); 1495 return QString::fromUtf8(s); 1496 #else 1497 return QString::fromLocal8Bit(error.asString()); 1498 #endif 1499 } 1500 1501 QString Formatting::prettyAlgorithmName(const std::string &algorithm) 1502 { 1503 static const std::map<std::string, QString> displayNames = { 1504 {"brainpoolP256r1", i18nc("@info", "ECC (Brainpool P-256)")}, 1505 {"brainpoolP384r1", i18nc("@info", "ECC (Brainpool P-384)")}, 1506 {"brainpoolP512r1", i18nc("@info", "ECC (Brainpool P-512)")}, 1507 {"curve25519", i18nc("@info", "ECC (Curve25519)")}, 1508 {"curve448", i18nc("@info", "ECC (Curve448)")}, 1509 {"ed25519", i18nc("@info", "ECC (Ed25519)")}, 1510 {"ed448", i18nc("@info", "ECC (Ed448)")}, 1511 {"cv25519", i18nc("@info", "ECC (Cv25519)")}, 1512 {"cv448", i18nc("@info", "ECC (Cv448)")}, 1513 {"nistp256", i18nc("@info", "ECC (NIST P-256)")}, 1514 {"nistp384", i18nc("@info", "ECC (NIST P-384)")}, 1515 {"nistp521", i18nc("@info", "ECC (NIST P-521)")}, 1516 {"rsa2048", i18nc("@info", "RSA 2048")}, 1517 {"rsa3072", i18nc("@info", "RSA 3072")}, 1518 {"rsa4096", i18nc("@info", "RSA 4096")}, 1519 {"dsa1024", i18nc("@info", "DSA 1024")}, 1520 {"dsa2048", i18nc("@info", "DSA 2048")}, 1521 {"elg1024", i18nc("@info", "Elgamal 1024")}, 1522 {"elg2048", i18nc("@info", "Elgamal 2048")}, 1523 {"elg3072", i18nc("@info", "Elgamal 3072")}, 1524 {"elg4096", i18nc("@info", "Elgamal 4096")}, 1525 }; 1526 const auto it = displayNames.find(algorithm); 1527 return (it != displayNames.end()) ? it->second : i18nc("@info", "Unknown algorithm"); 1528 }