File indexing completed on 2024-05-26 05:24:35

0001 /*
0002     utils/keyhelpers.cpp
0003 
0004     This file is part of libkleopatra, the KDE keymanagement library
0005     SPDX-FileCopyrightText: 2022 g10 Code GmbH
0006     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include <config-libkleo.h>
0012 
0013 #include "keyhelpers.h"
0014 
0015 #include <libkleo/algorithm.h>
0016 #include <libkleo/compat.h>
0017 #include <libkleo/keycache.h>
0018 
0019 #include <libkleo_debug.h>
0020 
0021 #include <QDate>
0022 
0023 // needed for GPGME_VERSION_NUMBER
0024 #include <gpgme.h>
0025 
0026 #include <iterator>
0027 
0028 using namespace Kleo;
0029 using namespace GpgME;
0030 
0031 namespace
0032 {
0033 bool havePublicKeyForSignature(const GpgME::UserID::Signature &signature)
0034 {
0035     // GnuPG returns status "NoPublicKey" for missing signing keys, but also
0036     // for expired or revoked signing keys.
0037     return (signature.status() != GpgME::UserID::Signature::NoPublicKey) //
0038         || !KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID()).isNull();
0039 }
0040 
0041 auto _getMissingSignerKeyIds(const std::vector<GpgME::UserID::Signature> &signatures)
0042 {
0043     return std::accumulate(std::begin(signatures), std::end(signatures), std::set<QString>{}, [](auto &keyIds, const auto &signature) {
0044         if (!havePublicKeyForSignature(signature)) {
0045             keyIds.insert(QLatin1StringView{signature.signerKeyID()});
0046         }
0047         return keyIds;
0048     });
0049 }
0050 }
0051 
0052 std::set<QString> Kleo::getMissingSignerKeyIds(const std::vector<GpgME::UserID> &userIds)
0053 {
0054     return std::accumulate(std::begin(userIds), std::end(userIds), std::set<QString>{}, [](auto &keyIds, const auto &userID) {
0055         if (!userID.isBad()) {
0056             const auto newKeyIds = _getMissingSignerKeyIds(userID.signatures());
0057             std::copy(std::begin(newKeyIds), std::end(newKeyIds), std::inserter(keyIds, std::end(keyIds)));
0058         }
0059         return keyIds;
0060     });
0061 }
0062 
0063 std::set<QString> Kleo::getMissingSignerKeyIds(const std::vector<GpgME::Key> &keys)
0064 {
0065     return std::accumulate(std::begin(keys), std::end(keys), std::set<QString>{}, [](auto &keyIds, const auto &key) {
0066         if (!key.isBad()) {
0067             const auto newKeyIds = getMissingSignerKeyIds(key.userIDs());
0068             std::copy(std::begin(newKeyIds), std::end(newKeyIds), std::inserter(keyIds, std::end(keyIds)));
0069         }
0070         return keyIds;
0071     });
0072 }
0073 
0074 bool Kleo::isRemoteKey(const GpgME::Key &key)
0075 {
0076     // a remote key looked up via WKD has key list mode Local; therefore we also look for the key in the local key ring
0077     return (key.keyListMode() == GpgME::Extern) || KeyCache::instance()->findByFingerprint(key.primaryFingerprint()).isNull();
0078 }
0079 
0080 GpgME::UserID::Validity Kleo::minimalValidityOfNotRevokedUserIDs(const Key &key)
0081 {
0082     const std::vector<UserID> userIDs = key.userIDs();
0083     const int minValidity = std::accumulate(userIDs.begin(), userIDs.end(), UserID::Ultimate + 1, [](int validity, const UserID &userID) {
0084         return userID.isRevoked() ? validity : std::min(validity, static_cast<int>(userID.validity()));
0085     });
0086     return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
0087 }
0088 
0089 GpgME::UserID::Validity Kleo::maximalValidityOfUserIDs(const Key &key)
0090 {
0091     const auto userIDs = key.userIDs();
0092     const int maxValidity = std::accumulate(userIDs.begin(), userIDs.end(), 0, [](int validity, const UserID &userID) {
0093         return std::max(validity, static_cast<int>(userID.validity()));
0094     });
0095     return static_cast<UserID::Validity>(maxValidity);
0096 }
0097 
0098 bool Kleo::allUserIDsHaveFullValidity(const GpgME::Key &key)
0099 {
0100     return minimalValidityOfNotRevokedUserIDs(key) >= UserID::Full;
0101 }
0102 
0103 namespace
0104 {
0105 bool isLastValidUserID(const GpgME::UserID &userId)
0106 {
0107     if (Kleo::isRevokedOrExpired(userId)) {
0108         return false;
0109     }
0110     const auto userIds = userId.parent().userIDs();
0111     const int numberOfValidUserIds = std::count_if(std::begin(userIds), std::end(userIds), [](const auto &u) {
0112         return !Kleo::isRevokedOrExpired(u);
0113     });
0114     return numberOfValidUserIds == 1;
0115 }
0116 
0117 bool hasValidUserID(const GpgME::Key &key)
0118 {
0119     return Kleo::any_of(key.userIDs(), [](const auto &u) {
0120         return !Kleo::isRevokedOrExpired(u);
0121     });
0122 }
0123 }
0124 
0125 bool Kleo::isSelfSignature(const GpgME::UserID::Signature &signature)
0126 {
0127     return !qstrcmp(signature.parent().parent().keyID(), signature.signerKeyID());
0128 }
0129 
0130 bool Kleo::isRevokedOrExpired(const GpgME::UserID &userId)
0131 {
0132     if (userId.isRevoked() || userId.parent().isExpired()) {
0133         return true;
0134     }
0135     const auto sigs = userId.signatures();
0136     std::vector<GpgME::UserID::Signature> selfSigs;
0137     std::copy_if(std::begin(sigs), std::end(sigs), std::back_inserter(selfSigs), &Kleo::isSelfSignature);
0138     std::sort(std::begin(selfSigs), std::end(selfSigs));
0139     // check the most recent signature
0140     const auto sig = !selfSigs.empty() ? selfSigs.back() : GpgME::UserID::Signature{};
0141     return !sig.isNull() && (sig.isRevokation() || sig.isExpired());
0142 }
0143 
0144 bool Kleo::isExpired(const UserID &userID)
0145 {
0146     if (userID.parent().isExpired()) {
0147         return true;
0148     }
0149     const auto sigs = userID.signatures();
0150     std::vector<GpgME::UserID::Signature> selfSigs;
0151     std::copy_if(std::begin(sigs), std::end(sigs), std::back_inserter(selfSigs), &Kleo::isSelfSignature);
0152     std::sort(std::begin(selfSigs), std::end(selfSigs));
0153     // check the most recent signature
0154     const auto sig = !selfSigs.empty() ? selfSigs.back() : GpgME::UserID::Signature{};
0155     return !sig.isNull() && sig.isExpired();
0156 }
0157 
0158 bool Kleo::canCreateCertifications(const GpgME::Key &key)
0159 {
0160     return Kleo::keyHasCertify(key) && canBeUsedForSecretKeyOperations(key);
0161 }
0162 
0163 bool Kleo::canBeCertified(const GpgME::Key &key)
0164 {
0165     return key.protocol() == GpgME::OpenPGP //
0166         && !key.isBad() //
0167         && hasValidUserID(key);
0168 }
0169 
0170 namespace
0171 {
0172 static inline bool subkeyHasSecret(const GpgME::Subkey &subkey)
0173 {
0174 #if GPGME_VERSION_NUMBER >= 0x011102 // 1.17.2
0175     // we need to check the primary subkey because Key::hasSecret() is also true if just the secret key stub of an offline key is available
0176     return subkey.isSecret();
0177 #else
0178     // older versions of GpgME did not always set the secret flag for card keys
0179     return subkey.isSecret() || subkey.isCardKey();
0180 #endif
0181 }
0182 }
0183 
0184 bool Kleo::canBeUsedForEncryption(const GpgME::Key &key)
0185 {
0186     return !key.isBad() && Kleo::any_of(key.subkeys(), [](const auto &subkey) {
0187         return subkey.canEncrypt() && !subkey.isBad();
0188     });
0189 }
0190 
0191 bool Kleo::canBeUsedForSigning(const GpgME::Key &key)
0192 {
0193     return !key.isBad() && Kleo::any_of(key.subkeys(), [](const auto &subkey) {
0194         return subkey.canSign() && !subkey.isBad() && subkeyHasSecret(subkey);
0195     });
0196 }
0197 
0198 bool Kleo::canBeUsedForSecretKeyOperations(const GpgME::Key &key)
0199 {
0200     return subkeyHasSecret(key.subkey(0));
0201 }
0202 
0203 bool Kleo::canRevokeUserID(const GpgME::UserID &userId)
0204 {
0205     return (!userId.isNull() //
0206             && userId.parent().protocol() == GpgME::OpenPGP //
0207             && !isLastValidUserID(userId));
0208 }
0209 
0210 bool Kleo::isSecretKeyStoredInKeyRing(const GpgME::Key &key)
0211 {
0212     return key.subkey(0).isSecret() && !key.subkey(0).isCardKey();
0213 }
0214 
0215 bool Kleo::userHasCertificationKey()
0216 {
0217     const auto secretKeys = KeyCache::instance()->secretKeys();
0218     return Kleo::any_of(secretKeys, [](const auto &k) {
0219         return (k.protocol() == GpgME::OpenPGP) && canCreateCertifications(k);
0220     });
0221 }
0222 
0223 Kleo::CertificationRevocationFeasibility Kleo::userCanRevokeCertification(const GpgME::UserID::Signature &certification)
0224 {
0225     const auto certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(certification.signerKeyID());
0226     const bool isSelfSignature = qstrcmp(certification.parent().parent().keyID(), certification.signerKeyID()) == 0;
0227     if (!certificationKey.hasSecret()) {
0228         return CertificationNotMadeWithOwnKey;
0229     } else if (isSelfSignature) {
0230         return CertificationIsSelfSignature;
0231     } else if (certification.isRevokation()) {
0232         return CertificationIsRevocation;
0233     } else if (certification.isExpired()) {
0234         return CertificationIsExpired;
0235     } else if (certification.isInvalid()) {
0236         return CertificationIsInvalid;
0237     } else if (!canCreateCertifications(certificationKey)) {
0238         return CertificationKeyNotAvailable;
0239     }
0240     return CertificationCanBeRevoked;
0241 }
0242 
0243 bool Kleo::userCanRevokeCertifications(const GpgME::UserID &userId)
0244 {
0245     if (userId.numSignatures() == 0) {
0246         qCWarning(LIBKLEO_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available";
0247     }
0248     return Kleo::any_of(userId.signatures(), [](const auto &certification) {
0249         return userCanRevokeCertification(certification) == CertificationCanBeRevoked;
0250     });
0251 }
0252 
0253 bool Kleo::userIDBelongsToKey(const GpgME::UserID &userID, const GpgME::Key &key)
0254 {
0255     return !qstricmp(userID.parent().primaryFingerprint(), key.primaryFingerprint());
0256 }
0257 
0258 static time_t creationDate(const GpgME::UserID &uid)
0259 {
0260     // returns the date of the first self-signature
0261     for (unsigned int i = 0, numSignatures = uid.numSignatures(); i < numSignatures; ++i) {
0262         const auto sig = uid.signature(i);
0263         if (Kleo::isSelfSignature(sig)) {
0264             return sig.creationTime();
0265         }
0266     }
0267     return 0;
0268 }
0269 
0270 bool Kleo::userIDsAreEqual(const GpgME::UserID &lhs, const GpgME::UserID &rhs)
0271 {
0272     return (qstrcmp(lhs.parent().primaryFingerprint(), rhs.parent().primaryFingerprint()) == 0 //
0273             && qstrcmp(lhs.id(), rhs.id()) == 0 //
0274             && creationDate(lhs) == creationDate(rhs));
0275 }
0276 
0277 static inline bool isOpenPGPCertification(const GpgME::UserID::Signature &sig)
0278 {
0279     // certification class is 0x10, ..., 0x13
0280     return (sig.certClass() & ~0x03) == 0x10;
0281 }
0282 
0283 static bool isOpenPGPCertificationByUser(const GpgME::UserID::Signature &sig)
0284 {
0285     if (!isOpenPGPCertification(sig)) {
0286         return false;
0287     }
0288     const auto certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID());
0289     return certificationKey.ownerTrust() == Key::Ultimate;
0290 }
0291 
0292 bool Kleo::userIDIsCertifiedByUser(const GpgME::UserID &userId)
0293 {
0294     if (userId.parent().protocol() != GpgME::OpenPGP) {
0295         qCWarning(LIBKLEO_LOG) << __func__ << "not called with OpenPGP key";
0296         return false;
0297     }
0298     if (userId.numSignatures() == 0) {
0299         qCWarning(LIBKLEO_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available";
0300     }
0301     for (unsigned int i = 0, numSignatures = userId.numSignatures(); i < numSignatures; ++i) {
0302         const auto sig = userId.signature(i);
0303         if ((sig.status() == UserID::Signature::NoError) && !sig.isBad() && sig.isExportable() && isOpenPGPCertificationByUser(sig)) {
0304             return true;
0305         }
0306     }
0307     return false;
0308 }