12#include <config-libkleo.h>
14#include "formatting.h"
18#include "compliance.h"
19#include "cryptoconfig.h"
21#include "keyhelpers.h"
23#include <libkleo/dn.h>
24#include <libkleo/keycache.h>
25#include <libkleo/keygroup.h>
27#include <libkleo_debug.h>
29#include <KEmailAddress>
30#include <KLocalizedString>
32#include <QGpgME/CryptoConfig>
33#include <QGpgME/Protocol>
38#include <QRegularExpression>
41#include <gpgme++/importresult.h>
42#include <gpgme++/key.h>
53QIcon iconForValidityAndCompliance(UserID::Validity validity,
bool isCompliant)
56 case UserID::Ultimate:
58 case UserID::Marginal:
59 return isCompliant ? Formatting::successIcon() : Formatting::infoIcon();
61 return Formatting::errorIcon();
62 case UserID::Undefined:
65 return Formatting::infoIcon();
68QIcon iconForValidity(
const UserID &userId)
70 const bool keyIsCompliant = !DeVSCompliance::isActive() ||
71 (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(userId.parent()));
72 return iconForValidityAndCompliance(userId.validity(), keyIsCompliant);
76QIcon Formatting::IconProvider::icon(
const GpgME::Key &key)
const
78 return icon(key.userID(0));
81QIcon Formatting::IconProvider::icon(
const GpgME::UserID &userID)
const
83 if (usage.canEncrypt() && !Kleo::canBeUsedForEncryption(userID.parent())) {
84 return Formatting::errorIcon();
86 if (usage.canSign() && !Kleo::canBeUsedForSigning(userID.parent())) {
87 return Formatting::errorIcon();
89 if (userID.parent().isBad() || userID.isBad()) {
90 return Formatting::errorIcon();
92 if (Kleo::isRevokedOrExpired(userID)) {
93 return Formatting::errorIcon();
95 return iconForValidity(userID);
98QIcon Formatting::IconProvider::icon(
const KeyGroup &group)
const
100 if (usage.canEncrypt() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForEncryption)) {
101 return Formatting::errorIcon();
103 if (usage.canSign() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForSigning)) {
104 return Formatting::errorIcon();
106 return validityIcon(group);
109QIcon Formatting::successIcon()
114QIcon Formatting::infoIcon()
119QIcon Formatting::questionIcon()
124QIcon Formatting::unavailableIcon()
129QIcon Formatting::warningIcon()
134QIcon Formatting::errorIcon()
143QString Formatting::prettyName(
int proto,
const char *
id,
const char *name_,
const char *comment_)
145 if (proto == GpgME::OpenPGP) {
147 if (name.isEmpty()) {
154 return QStringLiteral(
"%1 (%2)").arg(name, comment);
157 if (proto == GpgME::CMS) {
158 const DN subject(
id);
161 return subject.prettyDN();
169QString Formatting::prettyNameAndEMail(
int proto,
const char *
id,
const char *name_,
const char *email_,
const char *comment_)
176 if (proto == GpgME::OpenPGP) {
177 if (name.isEmpty()) {
180 }
else if (comment.
isEmpty()) {
181 return QStringLiteral(
"<%1>").arg(email);
183 return QStringLiteral(
"(%2) <%1>").arg(email, comment);
190 return QStringLiteral(
"%1 (%2)").arg(name, comment);
194 return QStringLiteral(
"%1 <%2>").arg(name, email);
196 return QStringLiteral(
"%1 (%3) <%2>").arg(name, email, comment);
200 if (proto == GpgME::CMS) {
201 const DN subject(
id);
204 return subject.prettyDN();
211QString Formatting::prettyUserID(
const UserID &uid)
213 if (uid.parent().protocol() == GpgME::OpenPGP) {
214 return prettyNameAndEMail(uid);
217 if (
id.startsWith(
'<')) {
218 return prettyEMail(uid.email(), uid.id());
220 if (
id.startsWith(
'(')) {
228QString Formatting::prettyKeyID(
const char *
id)
236QString Formatting::prettyNameAndEMail(
const UserID &uid)
238 return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment());
241QString Formatting::prettyNameAndEMail(
const Key &key)
243 return prettyNameAndEMail(key.userID(0));
246QString Formatting::prettyName(
const Key &key)
248 return prettyName(key.userID(0));
251QString Formatting::prettyName(
const UserID &uid)
253 return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment());
256QString Formatting::prettyName(
const UserID::Signature &sig)
258 return prettyName(GpgME::OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment());
265QString Formatting::prettyEMail(
const Key &key)
267 for (
unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) {
268 const QString email = prettyEMail(key.userID(i));
276QString Formatting::prettyEMail(
const UserID &uid)
278 return prettyEMail(uid.email(), uid.id());
281QString Formatting::prettyEMail(
const UserID::Signature &sig)
283 return prettyEMail(sig.signerEmail(), sig.signerUserID());
286QString Formatting::prettyEMail(
const char *email_,
const char *
id)
294 return DN(
id)[QStringLiteral(
"EMAIL")].trimmed();
312template<
typename T_arg>
315 return QStringLiteral(
"<tr><th>%1:</th><td>%2</td></tr>").
arg(protect_whitespace(field), arg);
319 return QStringLiteral(
"<tr><th>%1:</th><td>%2</td></tr>").
arg(protect_whitespace(field), arg.
toHtmlEscaped());
326QString format_keytype(
const Key &key)
328 const Subkey subkey = key.subkey(0);
329 if (key.hasSecret()) {
330 return i18n(
"%1-bit %2 (secret key available)", subkey.length(),
QLatin1StringView(subkey.publicKeyAlgorithmAsString()));
336QString format_subkeytype(
const Subkey &subkey)
338 const auto algo = subkey.publicKeyAlgorithm();
340 if (algo == Subkey::AlgoECC || algo == Subkey::AlgoECDSA || algo == Subkey::AlgoECDH || algo == Subkey::AlgoEDDSA) {
346QString format_keyusage(
const Key &key)
349 if (Kleo::keyHasSign(key)) {
350 if (key.isQualified()) {
356 if (Kleo::keyHasEncrypt(key)) {
359 if (Kleo::keyHasCertify(key)) {
362 if (Kleo::keyHasAuthenticate(key)) {
368QString format_subkeyusage(
const Subkey &subkey)
371 if (subkey.canSign()) {
372 if (subkey.isQualified()) {
378 if (subkey.canEncrypt()) {
381 if (subkey.canCertify()) {
384 if (subkey.canAuthenticate()) {
390static QString time_t2string(time_t t)
403static QString toolTipInternal(
const GpgME::Key &key,
const GpgME::UserID &userID,
int flags)
405 if (flags == 0 || (key.protocol() != GpgME::CMS && key.protocol() != GpgME::OpenPGP)) {
409 const Subkey subkey = key.subkey(0);
412 if (flags & Formatting::Validity) {
413 if (key.protocol() == GpgME::OpenPGP || (key.keyListMode() & Validate)) {
414 if (key.isDisabled()) {
415 result =
i18n(
"Disabled");
416 }
else if (userID.isRevoked() || key.isRevoked()) {
417 result = make_red(
i18n(
"Revoked"));
418 }
else if (key.isExpired()) {
419 result = make_red(
i18n(
"Expired"));
420 }
else if (key.keyListMode() & GpgME::Validate) {
421 if (!userID.isNull()) {
422 if (userID.validity() >= UserID::Validity::Full) {
423 result =
i18n(
"User ID is certified.");
424 const auto compliance = Formatting::complianceStringForUserID(userID);
425 if (!compliance.isEmpty()) {
426 result += QStringLiteral(
"<br>") + compliance;
429 result =
i18n(
"User ID is not certified.");
432 unsigned int fullyTrusted = 0;
433 for (
const auto &uid : key.userIDs()) {
434 if (uid.validity() >= UserID::Validity::Full) {
438 if (fullyTrusted == key.numUserIDs()) {
439 result =
i18n(
"All User IDs are certified.");
440 const auto compliance = Formatting::complianceStringForKey(key);
441 if (!compliance.isEmpty()) {
442 result += QStringLiteral(
"<br>") + compliance;
445 result =
i18np(
"One User ID is not certified.",
"%1 User IDs are not certified.", key.numUserIDs() - fullyTrusted);
449 result =
i18n(
"The validity cannot be checked at the moment.");
452 result =
i18n(
"The validity cannot be checked at the moment.");
455 if (flags == Formatting::Validity) {
460 if (key.protocol() == GpgME::CMS) {
461 if (flags & Formatting::SerialNumber) {
462 result += format_row(
i18n(
"Serial number"), key.issuerSerial());
464 if (flags & Formatting::Issuer) {
465 result += format_row(
i18n(
"Issuer"), key.issuerName());
468 if (flags & Formatting::UserIDs) {
469 if (userID.isNull()) {
470 const std::vector<UserID> uids = key.userIDs();
472 result += format_row(key.protocol() == GpgME::CMS ?
i18n(
"Subject") :
i18n(
"User ID"), Formatting::prettyUserID(uids.front()));
474 if (uids.size() > 1) {
475 for (
auto it = uids.begin() + 1, end = uids.end(); it != end; ++it) {
476 if (!it->isRevoked() && !it->isInvalid()) {
477 result += format_row(
i18n(
"a.k.a."), Formatting::prettyUserID(*it));
482 result += format_row(key.protocol() == GpgME::CMS ?
i18n(
"Subject") :
i18n(
"User ID"), Formatting::prettyUserID(userID));
485 if (flags & Formatting::ExpiryDates) {
486 result += format_row(
i18n(
"Valid from"), time_t2string(subkey.creationTime()));
488 if (!subkey.neverExpires()) {
489 result += format_row(
i18n(
"Valid until"), time_t2string(subkey.expirationTime()));
493 if (flags & Formatting::CertificateType) {
494 result += format_row(
i18n(
"Type"), format_keytype(key));
496 if (flags & Formatting::CertificateUsage) {
497 result += format_row(
i18n(
"Usage"), format_keyusage(key));
499 if (flags & Formatting::KeyID) {
502 if (flags & Formatting::Fingerprint) {
503 result += format_row(
i18n(
"Fingerprint"), key.primaryFingerprint());
505 if (flags & Formatting::OwnerTrust) {
506 if (key.protocol() == GpgME::OpenPGP) {
507 result += format_row(
i18n(
"Certification trust"), Formatting::ownerTrustShort(key));
508 }
else if (key.isRoot()) {
509 result += format_row(
i18n(
"Trusted issuer?"), (userID.isNull() ? key.userID(0) : userID).validity() == UserID::Ultimate ?
i18n(
"Yes") :
i18n(
"No"));
512 if (flags & Formatting::StorageLocation) {
513 if (
const char *card = subkey.cardSerialNumber()) {
516 result += format_row(
i18n(
"Stored"),
i18nc(
"stored...",
"on this computer"));
519 if (flags & Formatting::Subkeys) {
520 for (
const auto &sub : key.subkeys()) {
522 result += format_row(
i18n(
"Subkey"), sub.fingerprint());
523 if (sub.isRevoked()) {
524 result += format_row(
i18n(
"Status"),
i18n(
"Revoked"));
525 }
else if (sub.isExpired()) {
526 result += format_row(
i18n(
"Status"),
i18n(
"Expired"));
528 if (flags & Formatting::ExpiryDates) {
529 result += format_row(
i18n(
"Valid from"), time_t2string(sub.creationTime()));
531 if (!sub.neverExpires()) {
532 result += format_row(
i18n(
"Valid until"), time_t2string(sub.expirationTime()));
536 if (flags & Formatting::CertificateType) {
537 result += format_row(
i18n(
"Type"), format_subkeytype(sub));
539 if (flags & Formatting::CertificateUsage) {
540 result += format_row(
i18n(
"Usage"), format_subkeyusage(sub));
542 if (flags & Formatting::StorageLocation) {
543 if (
const char *card = sub.cardSerialNumber()) {
546 result += format_row(
i18n(
"Stored"),
i18nc(
"stored...",
"on this computer"));
556QString Formatting::toolTip(
const Key &key,
int flags)
558 return toolTipInternal(key, UserID(), flags);
563template<
typename Container>
564QString getValidityStatement(
const Container &keys)
566 const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [](
const Key &key) {
567 return key.protocol() == GpgME::OpenPGP;
569 const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [](
const Key &key) {
570 return key.keyListMode() & Validate;
572 if (allKeysAreOpenPGP || allKeysAreValidated) {
573 const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad));
574 if (someKeysAreBad) {
575 return i18n(
"Some keys are revoked, expired, disabled, or invalid.");
577 const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity);
578 if (allKeysAreFullyValid) {
579 return i18n(
"All keys are certified.");
581 return i18n(
"Some keys are not certified.");
585 return i18n(
"The validity of the keys cannot be checked at the moment.");
589QString Formatting::toolTip(
const KeyGroup &group,
int flags)
591 static const unsigned int maxNumKeysForTooltip = 20;
593 if (group.isNull()) {
597 const KeyGroup::Keys &keys = group.keys();
598 if (keys.size() == 0) {
599 return i18nc(
"@info:tooltip",
"This group does not contain any keys.");
602 if (Kleo::any_of(keys, [](
const auto &key) {
603 return !key.hasEncrypt();
605 return i18nc(
"@info:tooltip",
"Some of the certificates in this group cannot be used for encryption. Using this group can lead to unexpected results.");
608 const QString validity = (flags & Validity) ? getValidityStatement(keys) :
QString();
609 if (flags == Validity) {
614 const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size();
617 result.
reserve(3 + 2 + numKeysForTooltip + 2);
618 if (!validity.isEmpty()) {
620 result.
push_back(validity.toHtmlEscaped());
621 result.
push_back(QStringLiteral(
"</p>"));
627 auto it = keys.cbegin();
628 for (
unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) {
632 if (keys.size() > numKeysForTooltip) {
634 +
i18ncp(
"this follows a list of keys",
"and 1 more key",
"and %1 more keys", keys.size() - numKeysForTooltip));
636 result.
push_back(QStringLiteral(
"</p>"));
641QString Formatting::toolTip(
const UserID &userID,
int flags)
643 return toolTipInternal(userID.parent(), userID, flags);
652static QDate time_t2date(time_t t)
660static QString accessible_date_format()
663 "date format suitable for screen readers; "
664 "d: day as a number without a leading zero, "
665 "MMMM: localized month name, "
666 "yyyy: year as a four digit number",
671QString expiration_date_string(
const T &tee,
const QString &noExpiration)
673 return tee.neverExpires() ? noExpiration : Formatting::dateString(time_t2date(tee.expirationTime()));
676QDate creation_date(
const T &tee)
678 return time_t2date(tee.creationTime());
681QDate expiration_date(
const T &tee)
683 return time_t2date(tee.expirationTime());
687QString Formatting::dateString(time_t t)
689 return dateString(time_t2date(t));
697QString Formatting::accessibleDate(time_t t)
699 return accessibleDate(time_t2date(t));
707QString Formatting::expirationDateString(
const Key &key,
const QString &noExpiration)
712 return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0)
713 ?
i18nc(
"@info the expiration date of the key is unknown",
"unknown")
714 : expiration_date_string(key.subkey(0), noExpiration);
717QString Formatting::expirationDateString(
const Subkey &subkey,
const QString &noExpiration)
719 return expiration_date_string(subkey, noExpiration);
722QString Formatting::expirationDateString(
const UserID::Signature &sig,
const QString &noExpiration)
724 return expiration_date_string(sig, noExpiration);
727QDate Formatting::expirationDate(
const Key &key)
729 return expiration_date(key.subkey(0));
732QDate Formatting::expirationDate(
const Subkey &subkey)
734 return expiration_date(subkey);
737QDate Formatting::expirationDate(
const UserID::Signature &sig)
739 return expiration_date(sig);
742QString Formatting::accessibleExpirationDate(
const Key &key,
const QString &noExpiration)
747 return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0)
748 ?
i18nc(
"@info the expiration date of the key is unknown",
"unknown")
749 : accessibleExpirationDate(key.subkey(0), noExpiration);
752QString Formatting::accessibleExpirationDate(
const Subkey &subkey,
const QString &noExpiration)
754 if (subkey.neverExpires()) {
755 return noExpiration.
isEmpty() ?
i18n(
"unlimited") : noExpiration;
757 return accessibleDate(expirationDate(subkey));
761QString Formatting::accessibleExpirationDate(
const UserID::Signature &sig,
const QString &noExpiration)
763 if (sig.neverExpires()) {
764 return noExpiration.
isEmpty() ?
i18n(
"unlimited") : noExpiration;
766 return accessibleDate(expirationDate(sig));
770QString Formatting::creationDateString(
const Key &key)
772 return dateString(creation_date(key.subkey(0)));
775QString Formatting::creationDateString(
const Subkey &subkey)
777 return dateString(creation_date(subkey));
780QString Formatting::creationDateString(
const UserID::Signature &sig)
782 return dateString(creation_date(sig));
785QDate Formatting::creationDate(
const Key &key)
787 return creation_date(key.subkey(0));
790QDate Formatting::creationDate(
const Subkey &subkey)
792 return creation_date(subkey);
795QDate Formatting::creationDate(
const UserID::Signature &sig)
797 return creation_date(sig);
800QString Formatting::accessibleCreationDate(
const Key &key)
802 return accessibleDate(creationDate(key));
805QString Formatting::accessibleCreationDate(
const Subkey &subkey)
807 return accessibleDate(creationDate(subkey));
814QString Formatting::displayName(GpgME::Protocol p)
816 if (p == GpgME::CMS) {
817 return i18nc(
"X.509/CMS encryption standard",
"S/MIME");
819 if (p == GpgME::OpenPGP) {
820 return i18n(
"OpenPGP");
822 return i18nc(
"Unknown encryption protocol",
"Unknown");
825QString Formatting::type(
const Key &key)
827 return displayName(key.protocol());
830QString Formatting::type(
const Subkey &subkey)
835QString Formatting::type(
const KeyGroup &group)
838 return i18nc(
"a group of keys/certificates",
"Group");
845QString Formatting::ownerTrustShort(
const Key &key)
847 return ownerTrustShort(key.ownerTrust());
850QString Formatting::ownerTrustShort(Key::OwnerTrust trust)
854 return i18nc(
"unknown trust level",
"unknown");
856 return i18n(
"untrusted");
858 return i18nc(
"marginal trust",
"marginal");
860 return i18nc(
"full trust",
"full");
862 return i18nc(
"ultimate trust",
"ultimate");
864 return i18nc(
"undefined trust",
"undefined");
866 Q_ASSERT(!
"unexpected owner trust value");
872QString Formatting::validityShort(
const Subkey &subkey)
874 if (subkey.isDisabled()) {
875 return i18n(
"disabled");
877 if (subkey.isRevoked()) {
878 return i18n(
"revoked");
880 if (subkey.isExpired()) {
881 return i18n(
"expired");
883 if (subkey.isInvalid()) {
884 return i18n(
"invalid");
886 return i18nc(
"as in 'this subkey is ok'",
"OK");
889QString Formatting::validityShort(
const UserID &uid)
891 if (uid.isRevoked()) {
892 return i18n(
"revoked");
894 if (uid.isInvalid()) {
895 return i18n(
"invalid");
897 switch (uid.validity()) {
898 case UserID::Unknown:
899 return i18nc(
"unknown trust level",
"unknown");
900 case UserID::Undefined:
901 return i18nc(
"undefined trust",
"undefined");
903 return i18n(
"untrusted");
904 case UserID::Marginal:
905 return i18nc(
"marginal trust",
"marginal");
907 return i18nc(
"full trust",
"full");
908 case UserID::Ultimate:
909 return i18nc(
"ultimate trust",
"ultimate");
914QString Formatting::validityShort(
const UserID::Signature &sig)
916 switch (sig.status()) {
917 case UserID::Signature::NoError:
918 if (!sig.isInvalid()) {
920 switch (sig.certClass()) {
925 return i18n(
"valid");
927 return i18n(
"revoked");
929 return i18n(
"class %1", sig.certClass());
934 case UserID::Signature::GeneralError:
935 return i18n(
"invalid");
936 case UserID::Signature::SigExpired:
937 return i18n(
"expired");
938 case UserID::Signature::KeyExpired:
939 return i18n(
"certificate expired");
940 case UserID::Signature::BadSignature:
941 return i18nc(
"fake/invalid signature",
"bad");
942 case UserID::Signature::NoPublicKey: {
945 const auto key = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID());
947 return i18n(
"no public key");
948 }
else if (key.isDisabled()) {
949 return i18n(
"key disabled");
950 }
else if (key.isRevoked()) {
951 return i18n(
"key revoked");
952 }
else if (key.isExpired()) {
953 return i18n(
"key expired");
956 return QStringLiteral(
"unknown");
962QIcon Formatting::validityIcon(
const UserID::Signature &sig)
964 switch (sig.status()) {
965 case UserID::Signature::NoError:
966 if (!sig.isInvalid()) {
968 switch (sig.certClass()) {
973 return Formatting::successIcon();
975 return Formatting::errorIcon();
982 case UserID::Signature::BadSignature:
983 case UserID::Signature::GeneralError:
984 return Formatting::errorIcon();
985 case UserID::Signature::SigExpired:
986 case UserID::Signature::KeyExpired:
987 return Formatting::infoIcon();
988 case UserID::Signature::NoPublicKey:
989 return Formatting::questionIcon();
994QString Formatting::formatKeyLink(
const Key &key)
999 return QStringLiteral(
"<a href=\"key:%1\">%2</a>").
arg(
QLatin1StringView(key.primaryFingerprint()), Formatting::prettyName(key));
1002QString Formatting::formatForComboBox(
const GpgME::Key &key)
1004 const QString name = prettyName(key);
1006 if (!
mail.isEmpty()) {
1012QString Formatting::nameAndEmailForSummaryLine(
const UserID &
id)
1014 Q_ASSERT(!
id.isNull());
1016 const QString email = Formatting::prettyEMail(
id);
1017 const QString name = Formatting::prettyName(
id);
1019 if (name.isEmpty()) {
1024 return QStringLiteral(
"%1 <%2>").arg(name, email);
1028QString Formatting::nameAndEmailForSummaryLine(
const Key &key)
1030 Q_ASSERT(!key.isNull());
1032 const QString email = Formatting::prettyEMail(key);
1033 const QString name = Formatting::prettyName(key);
1035 if (name.isEmpty()) {
1040 return QStringLiteral(
"%1 <%2>").arg(name, email);
1044const char *Formatting::summaryToString(
const Signature::Summary summary)
1046 if (summary & Signature::Red) {
1049 if (summary & Signature::Green) {
1055QString Formatting::signatureToString(
const Signature &sig,
const Key &key)
1061 const bool red = (sig.summary() & Signature::Red);
1062 const bool valid = (sig.summary() & Signature::Valid);
1066 if (
const char *fpr = sig.fingerprint()) {
1067 return i18n(
"Bad signature by unknown certificate %1: %2",
QString::fromLatin1(fpr), Formatting::errorAsString(sig.status()));
1069 return i18n(
"Bad signature by an unknown certificate: %1", Formatting::errorAsString(sig.status()));
1072 return i18n(
"Bad signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status()));
1077 if (
const char *fpr = sig.fingerprint()) {
1080 return i18n(
"Good signature by an unknown certificate.");
1083 return i18n(
"Good signature by %1.", nameAndEmailForSummaryLine(key));
1086 }
else if (key.isNull()) {
1087 if (
const char *fpr = sig.fingerprint()) {
1088 return i18n(
"Invalid signature by unknown certificate %1: %2",
QString::fromLatin1(fpr), Formatting::errorAsString(sig.status()));
1090 return i18n(
"Invalid signature by an unknown certificate: %1", Formatting::errorAsString(sig.status()));
1093 return i18n(
"Invalid signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status()));
1103 const QString result = importMetaData(
import);
1111QString Formatting::importMetaData(
const Import &
import)
1113 if (
import.isNull()) {
1117 if (
import.
error().isCanceled()) {
1118 return i18n(
"The import of this certificate was canceled.");
1120 if (
import.
error()) {
1121 return i18n(
"An error occurred importing this certificate: %1", Formatting::errorAsString(
import.
error()));
1124 const unsigned int status =
import.status();
1125 if (
status & Import::NewKey) {
1126 return (
status & Import::ContainedSecretKey) ?
i18n(
"This certificate was new to your keystore. The secret key is available.")
1127 :
i18n(
"This certificate is new to your keystore.");
1131 if (
status & Import::NewUserIDs) {
1132 results.
push_back(
i18n(
"New user-ids were added to this certificate by the import."));
1134 if (
status & Import::NewSignatures) {
1135 results.
push_back(
i18n(
"New signatures were added to this certificate by the import."));
1137 if (
status & Import::NewSubkeys) {
1138 results.
push_back(
i18n(
"New subkeys were added to this certificate by the import."));
1141 return results.
empty() ?
i18n(
"The import contained no new data for this certificate. It is unchanged.") : results.join(
QLatin1Char(
'\n'));
1148QString Formatting::formatOverview(
const Key &key)
1150 return toolTip(key, AllOptions);
1153QString Formatting::usageString(
const Subkey &sub)
1156 if (sub.canCertify()) {
1157 usageStrings <<
i18n(
"Certify");
1159 if (sub.canSign()) {
1160 usageStrings <<
i18n(
"Sign");
1162 if (sub.canEncrypt()) {
1163 usageStrings <<
i18n(
"Encrypt");
1165 if (sub.canAuthenticate()) {
1166 usageStrings <<
i18n(
"Authenticate");
1168 if (sub.canRenc()) {
1169 usageStrings <<
i18nc(
"Means 'Additional Decryption Subkey'; Don't try translating that, though.",
"ADSK");
1174QString Formatting::summaryLine(
const UserID &
id)
1176 return i18nc(
"name <email> (validity, protocol, creation date)",
1177 "%1 (%2, %3, created: %4)",
1178 nameAndEmailForSummaryLine(
id),
1179 Formatting::complianceStringShort(
id),
1180 displayName(
id.parent().protocol()),
1181 Formatting::creationDateString(
id.parent()));
1184QString Formatting::summaryLine(
const Key &key)
1186 return nameAndEmailForSummaryLine(key) +
QLatin1Char(
' ')
1187 +
i18nc(
"(validity, protocol, creation date)",
1188 "(%1, %2, created: %3)",
1189 Formatting::complianceStringShort(key),
1190 displayName(key.protocol()),
1191 Formatting::creationDateString(key));
1194QString Formatting::summaryLine(
const KeyGroup &group)
1196 switch (group.source()) {
1197 case KeyGroup::ApplicationConfig:
1198 case KeyGroup::GnuPGConfig:
1199 return i18ncp(
"name of group of keys (n key(s), validity)",
1202 group.keys().size(),
1204 Formatting::complianceStringShort(group));
1205 case KeyGroup::Tags:
1206 return i18ncp(
"name of group of keys (n key(s), validity, tag)",
1207 "%2 (1 key, %3, tag)",
1208 "%2 (%1 keys, %3, tag)",
1209 group.keys().size(),
1211 Formatting::complianceStringShort(group));
1213 return i18ncp(
"name of group of keys (n key(s), validity, group ...)",
1214 "%2 (1 key, %3, unknown origin)",
1215 "%2 (%1 keys, %3, unknown origin)",
1216 group.keys().size(),
1218 Formatting::complianceStringShort(group));
1223QIcon Formatting::iconForUid(
const UserID &uid)
1225 if (Kleo::isRevokedOrExpired(uid)) {
1226 return Formatting::errorIcon();
1228 return iconForValidity(uid);
1231QString Formatting::validity(
const UserID &uid)
1233 switch (uid.validity()) {
1234 case UserID::Ultimate:
1235 return i18n(
"The certificate is marked as your own.");
1237 return i18n(
"The certificate belongs to this recipient.");
1238 case UserID::Marginal:
1239 return i18n(
"The trust model indicates marginally that the certificate belongs to this recipient.");
1241 return i18n(
"This certificate should not be used.");
1242 case UserID::Undefined:
1243 case UserID::Unknown:
1245 return i18n(
"There is no indication that this certificate belongs to this recipient.");
1249QString Formatting::validity(
const KeyGroup &group)
1251 if (group.isNull()) {
1255 const KeyGroup::Keys &keys = group.keys();
1256 if (keys.size() == 0) {
1257 return i18n(
"This group does not contain any keys.");
1260 return getValidityStatement(keys);
1265template<
typename Container>
1266UserID::Validity minimalValidity(
const Container &keys)
1268 const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [](
int validity,
const Key &key) {
1269 return std::min<int>(validity, minimalValidityOfNotRevokedUserIDs(key));
1271 return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::
Unknown;
1274template<
typename Container>
1275bool allKeysAreCompliant(
const Container &keys)
1277 if (!DeVSCompliance::isActive()) {
1280 if (!DeVSCompliance::isCompliant()) {
1283 return Kleo::all_of(keys, DeVSCompliance::keyIsCompliant);
1287QIcon Formatting::validityIcon(
const KeyGroup &group)
1289 if (Kleo::any_of(group.keys(), std::mem_fn(&Key::isBad))) {
1290 return Formatting::errorIcon();
1292 return iconForValidityAndCompliance(minimalValidity(group.keys()), allKeysAreCompliant(group.keys()));
1295QString Formatting::complianceMode()
1297 const auto complianceValue = getCryptoConfigStringValue(
"gpg",
"compliance");
1301QString Formatting::complianceStringForKey(
const GpgME::Key &key)
1305 if (DeVSCompliance::isCompliant()) {
1306 return isRemoteKey(key)
1307 ?
i18nc(
"@info the compliance of the key with certain requirements is unknown",
"unknown")
1308 : DeVSCompliance::name(DeVSCompliance::keyIsCompliant(key));
1313QString Formatting::complianceStringForUserID(
const GpgME::UserID &userID)
1317 if (DeVSCompliance::isCompliant()) {
1318 return isRemoteKey(userID.parent())
1319 ?
i18nc(
"@info the compliance of the key with certain requirements is unknown",
"unknown")
1320 : DeVSCompliance::name(DeVSCompliance::userIDIsCompliant(userID));
1325QString Formatting::complianceStringShort(
const GpgME::UserID &
id)
1327 if (DeVSCompliance::isCompliant() && DeVSCompliance::userIDIsCompliant(
id)) {
1328 return QStringLiteral(
"★ ") + DeVSCompliance::name(
true);
1330 const bool keyValidityChecked = (
id.parent().keyListMode() & GpgME::Validate);
1331 if (keyValidityChecked &&
id.validity() >= UserID::Full) {
1332 return i18nc(
"As in 'this user ID is valid.'",
"certified");
1334 if (
id.parent().isDisabled()) {
1335 return i18n(
"disabled");
1337 if (
id.parent().isRevoked() ||
id.isRevoked()) {
1338 return i18n(
"revoked");
1340 if (
id.parent().isExpired() || isExpired(
id)) {
1341 return i18n(
"expired");
1343 if (
id.parent().isInvalid() ||
id.isInvalid()) {
1344 return i18n(
"invalid");
1346 if (keyValidityChecked) {
1347 return i18nc(
"As in 'this user ID is not certified'",
"not certified");
1350 return i18nc(
"The validity of this user ID has not been/could not be checked",
"not checked");
1353QString Formatting::complianceStringShort(
const GpgME::Key &key)
1355 if (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(key)) {
1356 return QStringLiteral(
"★ ") + DeVSCompliance::name(
true);
1358 const bool keyValidityChecked = (key.keyListMode() & GpgME::Validate);
1359 if (key.isDisabled()) {
1360 return i18n(
"disabled");
1362 if (key.isRevoked()) {
1363 return i18n(
"revoked");
1365 if (key.isExpired()) {
1366 return i18n(
"expired");
1368 if (key.isInvalid()) {
1369 return i18n(
"invalid");
1371 if (keyValidityChecked && Kleo::allUserIDsHaveFullValidity(key)) {
1372 return i18nc(
"As in all user IDs are valid.",
"certified");
1374 if (keyValidityChecked) {
1375 return i18nc(
"As in not all user IDs are valid.",
"not certified");
1378 return i18nc(
"The validity of the user IDs has not been/could not be checked",
"not checked");
1381QString Formatting::complianceStringShort(
const KeyGroup &group)
1383 const KeyGroup::Keys &keys = group.keys();
1385 const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity);
1386 if (allKeysFullyValid) {
1387 return i18nc(
"As in all keys are valid.",
"all certified");
1390 return i18nc(
"As in not all keys are valid.",
"not all certified");
1393QString Formatting::prettyID(
const char *
id)
1399 if (ret.
size() == 64) {
1407 if (ret.
size() == 49) {
1413QString Formatting::accessibleHexID(
const char *
id)
1415 static const QRegularExpression groupOfFourRegExp{QStringLiteral(
"(?:(.)(.)(.)(.))")};
1416 static const QRegularExpression groupOfFiveRegExp{QStringLiteral(
"(?:(.)(.)(.)(.)(.))")};
1420 if (ret.
size() == 64) {
1422 return ret.
replace(groupOfFiveRegExp, QStringLiteral(
"\\1 \\2 \\3 \\4 \\5, ")).
chopped(2);
1425 ret = ret.
replace(groupOfFourRegExp, QStringLiteral(
"\\1 \\2 \\3 \\4, ")).
chopped(2);
1430QString Formatting::origin(
int o)
1434 return i18n(
"Keyserver");
1435 case Key::OriginDane:
1436 return QStringLiteral(
"DANE");
1437 case Key::OriginWKD:
1438 return QStringLiteral(
"WKD");
1439 case Key::OriginURL:
1440 return QStringLiteral(
"URL");
1441 case Key::OriginFile:
1442 return i18n(
"File import");
1443 case Key::OriginSelf:
1444 return i18n(
"Generated");
1445 case Key::OriginOther:
1446 case Key::OriginUnknown:
1454QString formatTrustScope(
const char *trustScope)
1456 static const QRegularExpression escapedNonAlphaNum{QStringLiteral(R
"(\\([^0-9A-Za-z]))")};
1459 if (scopeRegExp.startsWith(u
"<[^>]+[@.]") && scopeRegExp.endsWith(u
">$")) {
1461 auto domain = scopeRegExp.mid(10, scopeRegExp.size() - 10 - 2);
1462 domain.replace(escapedNonAlphaNum, QStringLiteral(R
"(\1)"));
1469QString Formatting::trustSignatureDomain(
const GpgME::UserID::Signature &sig)
1471 return formatTrustScope(sig.trustScope());
1474QString Formatting::trustSignature(
const GpgME::UserID::Signature &sig)
1476 switch (sig.trustValue()) {
1477 case TrustSignatureTrust::Partial:
1478 return i18nc(
"Certifies this key as partially trusted introducer for 'domain name'.",
1479 "Certifies this key as partially trusted introducer for '%1'.",
1480 trustSignatureDomain(sig));
1481 case TrustSignatureTrust::Complete:
1482 return i18nc(
"Certifies this key as fully trusted introducer for 'domain name'.",
1483 "Certifies this key as fully trusted introducer for '%1'.",
1484 trustSignatureDomain(sig));
1490QString Formatting::errorAsString(
const GpgME::Error &error)
1494#if GPGMEPP_ERROR_HAS_ASSTDSTRING
1495 const std::string s =
error.asStdString();
1496 qCDebug(LIBKLEO_LOG) << __func__ <<
"gettext_use_utf8(-1) returns" << gettext_use_utf8(-1);
1497 qCDebug(LIBKLEO_LOG) << __func__ <<
"error:" << s;
1501 const char *s =
error.asString();
1502 qCDebug(LIBKLEO_LOG) << __func__ <<
"gettext_use_utf8(-1) returns" << gettext_use_utf8(-1);
1503 qCDebug(LIBKLEO_LOG) << __func__ <<
"error:" << s;
1508#if GPGMEPP_ERROR_HAS_ASSTDSTRING
1509 const std::string s =
error.asStdString();
1517QString Formatting::prettyAlgorithmName(
const std::string &algorithm)
1519 static const std::map<std::string, QString> displayNames = {
1520 {
"brainpoolP256r1",
i18nc(
"@info",
"ECC (Brainpool P-256)")},
1521 {
"brainpoolP384r1",
i18nc(
"@info",
"ECC (Brainpool P-384)")},
1522 {
"brainpoolP512r1",
i18nc(
"@info",
"ECC (Brainpool P-512)")},
1523 {
"curve25519",
i18nc(
"@info",
"ECC (Curve25519)")},
1524 {
"curve448",
i18nc(
"@info",
"ECC (Curve448)")},
1525 {
"ed25519",
i18nc(
"@info",
"ECC (Ed25519)")},
1526 {
"ed448",
i18nc(
"@info",
"ECC (Ed448)")},
1527 {
"cv25519",
i18nc(
"@info",
"ECC (Cv25519)")},
1528 {
"cv448",
i18nc(
"@info",
"ECC (Cv448)")},
1529 {
"nistp256",
i18nc(
"@info",
"ECC (NIST P-256)")},
1530 {
"nistp384",
i18nc(
"@info",
"ECC (NIST P-384)")},
1531 {
"nistp521",
i18nc(
"@info",
"ECC (NIST P-521)")},
1532 {
"rsa1024",
i18nc(
"@info",
"RSA 1024")},
1533 {
"rsa2048",
i18nc(
"@info",
"RSA 2048")},
1534 {
"rsa3072",
i18nc(
"@info",
"RSA 3072")},
1535 {
"rsa4096",
i18nc(
"@info",
"RSA 4096")},
1536 {
"dsa1024",
i18nc(
"@info",
"DSA 1024")},
1537 {
"dsa2048",
i18nc(
"@info",
"DSA 2048")},
1538 {
"elg1024",
i18nc(
"@info",
"Elgamal 1024")},
1539 {
"elg2048",
i18nc(
"@info",
"Elgamal 2048")},
1540 {
"elg3072",
i18nc(
"@info",
"Elgamal 3072")},
1541 {
"elg4096",
i18nc(
"@info",
"Elgamal 4096")},
1543 const auto it = displayNames.find(algorithm);
1547static QString formatValidSignatureWithTrustLevel(
const GpgME::UserID &
id)
1552 switch (
id.validity()) {
1553 case GpgME::UserID::Marginal:
1554 return i18n(
"The signature is valid but the trust in the certificate's validity is only marginal.");
1555 case GpgME::UserID::Full:
1556 return i18n(
"The signature is valid and the certificate's validity is fully trusted.");
1557 case GpgME::UserID::Ultimate:
1558 return i18n(
"The signature is valid and the certificate's validity is ultimately trusted.");
1559 case GpgME::UserID::Never:
1560 return i18n(
"The signature is valid but the certificate's validity is <em>not trusted</em>.");
1561 case GpgME::UserID::Unknown:
1562 return i18n(
"The signature is valid but the certificate's validity is unknown.");
1563 case GpgME::UserID::Undefined:
1565 return i18n(
"The signature is valid but the certificate's validity is undefined.");
1571 return QStringLiteral(
"<a href=\"key:%1\">%2</a>").
arg(fpr, text.
toHtmlEscaped());
1574static QString renderKey(
const GpgME::Key &key)
1577 return i18n(
"Unknown certificate");
1581 i18nc(
"User ID (Key ID)",
"%1 (%2)", Formatting::prettyNameAndEMail(key), Formatting::prettyID(key.subkey(0).keyID())));
1584static QString formatSigningInformation(
const GpgME::Signature &sig,
const GpgME::Key &key)
1594 QStringLiteral(
"<br/><a href='certificate:%1'>%2</a>").arg(QString::fromLatin1(sig.fingerprint()), Formatting::prettyID(sig.fingerprint()));
1596 return i18nc(
"1 is a date",
"Signature created on %1 using an unknown certificate with fingerprint %2", QLocale().toString(dt), id);
1598 return i18n(
"Signature created using an unknown certificate with fingerprint %1",
id);
1602 text += i18nc(
"1 is a date",
"Signature created on %1 with certificate: %2", QLocale().toString(dt), renderKey(key));
1604 text += i18n(
"Signature created with certificate: %1", renderKey(key));
1607 if (Kleo::DeVSCompliance::isCompliant() && ((sig.summary() & GpgME::Signature::Valid) || (sig.summary() & GpgME::Signature::Green))) {
1608 text += (QStringLiteral(
"<br/>")
1609 + (sig.isDeVs() ? i18nc(
"%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
1610 "The signature is %1",
1611 Kleo::DeVSCompliance::name(true))
1612 : i18nc(
"%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
1613 "The signature <b>is not</b> %1.",
1614 Kleo::DeVSCompliance::name(true))));
1620static QString signatureSummaryToString(GpgME::Signature::Summary summary)
1622 if (summary & GpgME::Signature::None) {
1623 return i18n(
"Error: Signature not verified");
1624 }
else if ((summary & GpgME::Signature::Valid) || (summary & GpgME::Signature::Green)) {
1625 return i18n(
"Good signature");
1626 }
else if (summary & GpgME::Signature::KeyRevoked) {
1627 return i18n(
"Signing certificate was revoked");
1628 }
else if (summary & GpgME::Signature::KeyExpired) {
1629 return i18n(
"Signing certificate is expired");
1630 }
else if (summary & GpgME::Signature::KeyMissing) {
1631 return i18n(
"Certificate is not available");
1632 }
else if (summary & GpgME::Signature::SigExpired) {
1633 return i18n(
"Signature expired");
1634 }
else if (summary & GpgME::Signature::CrlMissing) {
1635 return i18n(
"CRL missing");
1636 }
else if (summary & GpgME::Signature::CrlTooOld) {
1637 return i18n(
"CRL too old");
1638 }
else if (summary & GpgME::Signature::BadPolicy) {
1639 return i18n(
"Bad policy");
1640 }
else if (summary & GpgME::Signature::SysError) {
1641 return i18n(
"System error");
1642 }
else if (summary & GpgME::Signature::Red) {
1643 return i18n(
"Bad signature");
1653 if (str[0] ==
'<' && str[str.
size() - 1] ==
'>') {
1654 return str.
mid(1, str.
size() - 2);
1659QString Formatting::email(
const GpgME::UserID &uid)
1661 if (uid.parent().protocol() == GpgME::OpenPGP) {
1664 return stripAngleBrackets(email).
toString();
1669 Q_ASSERT(uid.parent().protocol() == GpgME::CMS);
1672 if (!
id.isEmpty()) {
1674 return stripAngleBrackets(
id).
toString();
1676 return Kleo::DN(
id)[QStringLiteral(
"EMAIL")].trimmed();
1681static GpgME::UserID findUserIDByMailbox(
const GpgME::Key &key,
const QString &email)
1683 const auto userIDs{key.userIDs()};
1684 for (
const GpgME::UserID &
id : userIDs) {
1692QString Kleo::Formatting::prettySignature(
const GpgME::Signature &sig,
const QString &sender)
1698 const GpgME::Key key = Kleo::KeyCache::instance()->findSigner(sig);
1703 if (sig.summary() & GpgME::Signature::Valid) {
1704 GpgME::UserID
id = findUserIDByMailbox(key, sender);
1706 for (
int i = 0, count = key.userIDs().size(); i < count; i++) {
1714 return text + formatValidSignatureWithTrustLevel(!
id.isNull() ?
id : key.userID(0));
1718 if ((sig.summary() & GpgME::Signature::Red)) {
1719 const QString ret = text +
i18n(
"The signature is invalid: %1", signatureSummaryToString(sig.summary()));
1720 if (sig.summary() & GpgME::Signature::SysError) {
1721 return ret + QStringLiteral(
" (%1)").
arg(Kleo::Formatting::errorAsString(sig.status()));
1727 if ((sig.summary() & GpgME::Signature::KeyMissing)) {
1728 return text +
i18n(
"You can search the certificate on a keyserver or import it from a file.");
1732 if ((sig.validity() & GpgME::Signature::Validity::Undefined)
1733 || (sig.validity() & GpgME::Signature::Validity::Unknown)
1734 || (sig.summary() == GpgME::Signature::Summary::None)) {
1736 + (key.protocol() == GpgME::OpenPGP
1737 ?
i18n(
"The used key is not certified by you or any trusted person.")
1738 :
i18n(
"The used certificate is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown."));
1742 const QString ret = text +
i18n(
"The signature is invalid: %1", signatureSummaryToString(sig.summary()));
1743 if (sig.summary() & GpgME::Signature::SysError) {
1744 return ret + QStringLiteral(
" (%1)").
arg(Kleo::Formatting::errorAsString(sig.status()));
Q_SCRIPTABLE CaptureState status()
KCODECS_EXPORT EmailParseResult splitAddress(const QByteArray &address, QByteArray &displayName, QByteArray &addrSpec, QByteArray &comment)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
Capabilities capabilities()
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QAction * mail(const QObject *recvr, const char *slot, QObject *parent)
QByteArray fromStdString(const std::string &str)
QByteArray toPercentEncoding(const QByteArray &exclude, const QByteArray &include, char percent) const const
QByteArray trimmed() const const
QDateTime fromSecsSinceEpoch(qint64 secs)
bool isValid() const const
QIcon fromTheme(const QString &name)
bool isEmpty() const const
QLatin1StringView mid(qsizetype start, qsizetype length) const const
qsizetype size() const const
QString toString() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
QString toString(QDate date, FormatType format) const const
QString arg(Args &&... args) const const
QString chopped(qsizetype len) const const
QString fromLatin1(QByteArrayView str)
QString fromLocal8Bit(QByteArrayView str)
QString fromStdString(const std::string &str)
QString fromUtf8(QByteArrayView str)
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString simplified() const const
qsizetype size() const const
QString toHtmlEscaped() const const
QString toUpper() const const
QString trimmed() const const
void truncate(qsizetype position)
QString join(QChar separator) const const