Libkleo

keyhelpers.cpp
1/*
2 utils/keyhelpers.cpp
3
4 This file is part of libkleopatra, the KDE keymanagement library
5 SPDX-FileCopyrightText: 2022 g10 Code GmbH
6 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9*/
10
11#include <config-libkleo.h>
12
13#include "keyhelpers.h"
14
15#include <libkleo/algorithm.h>
16#include <libkleo/compat.h>
17#include <libkleo/keycache.h>
18
19#include <libkleo_debug.h>
20
21#include <QDate>
22
23// needed for GPGME_VERSION_NUMBER
24#include <gpgme.h>
25
26#include <iterator>
27
28using namespace Kleo;
29using namespace GpgME;
30
31namespace
32{
33bool havePublicKeyForSignature(const GpgME::UserID::Signature &signature)
34{
35 // GnuPG returns status "NoPublicKey" for missing signing keys, but also
36 // for expired or revoked signing keys.
37 return (signature.status() != GpgME::UserID::Signature::NoPublicKey) //
38 || !KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID()).isNull();
39}
40
41auto _getMissingSignerKeyIds(const std::vector<GpgME::UserID::Signature> &signatures)
42{
43 return std::accumulate(std::begin(signatures), std::end(signatures), std::set<QString>{}, [](auto &keyIds, const auto &signature) {
44 if (!havePublicKeyForSignature(signature)) {
45 keyIds.insert(QLatin1StringView{signature.signerKeyID()});
46 }
47 return keyIds;
48 });
49}
50}
51
52std::set<QString> Kleo::getMissingSignerKeyIds(const std::vector<GpgME::UserID> &userIds)
53{
54 return std::accumulate(std::begin(userIds), std::end(userIds), std::set<QString>{}, [](auto &keyIds, const auto &userID) {
55 if (!userID.isBad()) {
56 const auto newKeyIds = _getMissingSignerKeyIds(userID.signatures());
57 std::copy(std::begin(newKeyIds), std::end(newKeyIds), std::inserter(keyIds, std::end(keyIds)));
58 }
59 return keyIds;
60 });
61}
62
63std::set<QString> Kleo::getMissingSignerKeyIds(const std::vector<GpgME::Key> &keys)
64{
65 return std::accumulate(std::begin(keys), std::end(keys), std::set<QString>{}, [](auto &keyIds, const auto &key) {
66 if (!key.isBad()) {
67 const auto newKeyIds = getMissingSignerKeyIds(key.userIDs());
68 std::copy(std::begin(newKeyIds), std::end(newKeyIds), std::inserter(keyIds, std::end(keyIds)));
69 }
70 return keyIds;
71 });
72}
73
74bool Kleo::isRemoteKey(const GpgME::Key &key)
75{
76 // a remote key looked up via WKD has key list mode Local; therefore we also look for the key in the local key ring
77 return (key.keyListMode() == GpgME::Extern) || KeyCache::instance()->findByFingerprint(key.primaryFingerprint()).isNull();
78}
79
80GpgME::UserID::Validity Kleo::minimalValidityOfNotRevokedUserIDs(const Key &key)
81{
82 const std::vector<UserID> userIDs = key.userIDs();
83 const int minValidity = std::accumulate(userIDs.begin(), userIDs.end(), UserID::Ultimate + 1, [](int validity, const UserID &userID) {
84 return userID.isRevoked() ? validity : std::min(validity, static_cast<int>(userID.validity()));
85 });
86 return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
87}
88
89GpgME::UserID::Validity Kleo::maximalValidityOfUserIDs(const Key &key)
90{
91 const auto userIDs = key.userIDs();
92 const int maxValidity = std::accumulate(userIDs.begin(), userIDs.end(), 0, [](int validity, const UserID &userID) {
93 return std::max(validity, static_cast<int>(userID.validity()));
94 });
95 return static_cast<UserID::Validity>(maxValidity);
96}
97
98bool Kleo::allUserIDsHaveFullValidity(const GpgME::Key &key)
99{
100 return minimalValidityOfNotRevokedUserIDs(key) >= UserID::Full;
101}
102
103namespace
104{
105bool isLastValidUserID(const GpgME::UserID &userId)
106{
107 if (Kleo::isRevokedOrExpired(userId)) {
108 return false;
109 }
110 const auto userIds = userId.parent().userIDs();
111 const int numberOfValidUserIds = std::count_if(std::begin(userIds), std::end(userIds), [](const auto &u) {
112 return !Kleo::isRevokedOrExpired(u);
113 });
114 return numberOfValidUserIds == 1;
115}
116
117bool hasValidUserID(const GpgME::Key &key)
118{
119 return Kleo::any_of(key.userIDs(), [](const auto &u) {
120 return !Kleo::isRevokedOrExpired(u);
121 });
122}
123}
124
125bool Kleo::isSelfSignature(const GpgME::UserID::Signature &signature)
126{
127 return !qstrcmp(signature.parent().parent().keyID(), signature.signerKeyID());
128}
129
130bool Kleo::isRevokedOrExpired(const GpgME::UserID &userId)
131{
132 if (userId.isRevoked() || userId.parent().isExpired()) {
133 return true;
134 }
135 const auto sigs = userId.signatures();
136 std::vector<GpgME::UserID::Signature> selfSigs;
137 std::copy_if(std::begin(sigs), std::end(sigs), std::back_inserter(selfSigs), &Kleo::isSelfSignature);
138 std::sort(std::begin(selfSigs), std::end(selfSigs));
139 // check the most recent signature
140 const auto sig = !selfSigs.empty() ? selfSigs.back() : GpgME::UserID::Signature{};
141 return !sig.isNull() && (sig.isRevokation() || sig.isExpired());
142}
143
144bool Kleo::isExpired(const UserID &userID)
145{
146 if (userID.parent().isExpired()) {
147 return true;
148 }
149 const auto sigs = userID.signatures();
150 std::vector<GpgME::UserID::Signature> selfSigs;
151 std::copy_if(std::begin(sigs), std::end(sigs), std::back_inserter(selfSigs), &Kleo::isSelfSignature);
152 std::sort(std::begin(selfSigs), std::end(selfSigs));
153 // check the most recent signature
154 const auto sig = !selfSigs.empty() ? selfSigs.back() : GpgME::UserID::Signature{};
155 return !sig.isNull() && sig.isExpired();
156}
157
158bool Kleo::canCreateCertifications(const GpgME::Key &key)
159{
160 return Kleo::keyHasCertify(key) && canBeUsedForSecretKeyOperations(key);
161}
162
163bool Kleo::canBeCertified(const GpgME::Key &key)
164{
165 return key.protocol() == GpgME::OpenPGP //
166 && !key.isBad() //
167 && hasValidUserID(key);
168}
169
170namespace
171{
172static inline bool subkeyHasSecret(const GpgME::Subkey &subkey)
173{
174#if GPGME_VERSION_NUMBER >= 0x011102 // 1.17.2
175 // 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
176 return subkey.isSecret();
177#else
178 // older versions of GpgME did not always set the secret flag for card keys
179 return subkey.isSecret() || subkey.isCardKey();
180#endif
181}
182}
183
184bool Kleo::canBeUsedForEncryption(const GpgME::Key &key)
185{
186 return !key.isBad() && Kleo::any_of(key.subkeys(), [](const auto &subkey) {
187 return subkey.canEncrypt() && !subkey.isBad();
188 });
189}
190
191bool Kleo::canBeUsedForSigning(const GpgME::Key &key)
192{
193 return !key.isBad() && Kleo::any_of(key.subkeys(), [](const auto &subkey) {
194 return subkey.canSign() && !subkey.isBad() && subkeyHasSecret(subkey);
195 });
196}
197
198bool Kleo::canBeUsedForSecretKeyOperations(const GpgME::Key &key)
199{
200 return subkeyHasSecret(key.subkey(0));
201}
202
203bool Kleo::canRevokeUserID(const GpgME::UserID &userId)
204{
205 return (!userId.isNull() //
206 && userId.parent().protocol() == GpgME::OpenPGP //
207 && !isLastValidUserID(userId));
208}
209
210bool Kleo::isSecretKeyStoredInKeyRing(const GpgME::Key &key)
211{
212 return key.subkey(0).isSecret() && !key.subkey(0).isCardKey();
213}
214
215bool Kleo::userHasCertificationKey()
216{
217 const auto secretKeys = KeyCache::instance()->secretKeys();
218 return Kleo::any_of(secretKeys, [](const auto &k) {
219 return (k.protocol() == GpgME::OpenPGP) && canCreateCertifications(k);
220 });
221}
222
223Kleo::CertificationRevocationFeasibility Kleo::userCanRevokeCertification(const GpgME::UserID::Signature &certification)
224{
225 const auto certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(certification.signerKeyID());
226 const bool isSelfSignature = qstrcmp(certification.parent().parent().keyID(), certification.signerKeyID()) == 0;
227 if (!certificationKey.hasSecret()) {
228 return CertificationNotMadeWithOwnKey;
229 } else if (isSelfSignature) {
230 return CertificationIsSelfSignature;
231 } else if (certification.isRevokation()) {
232 return CertificationIsRevocation;
233 } else if (certification.isExpired()) {
234 return CertificationIsExpired;
235 } else if (certification.isInvalid()) {
236 return CertificationIsInvalid;
237 } else if (!canCreateCertifications(certificationKey)) {
238 return CertificationKeyNotAvailable;
239 }
240 return CertificationCanBeRevoked;
241}
242
243bool Kleo::userCanRevokeCertifications(const GpgME::UserID &userId)
244{
245 if (userId.numSignatures() == 0) {
246 qCWarning(LIBKLEO_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available";
247 }
248 return Kleo::any_of(userId.signatures(), [](const auto &certification) {
249 return userCanRevokeCertification(certification) == CertificationCanBeRevoked;
250 });
251}
252
253bool Kleo::userIDBelongsToKey(const GpgME::UserID &userID, const GpgME::Key &key)
254{
255 return !qstricmp(userID.parent().primaryFingerprint(), key.primaryFingerprint());
256}
257
258static time_t creationDate(const GpgME::UserID &uid)
259{
260 // returns the date of the first self-signature
261 for (unsigned int i = 0, numSignatures = uid.numSignatures(); i < numSignatures; ++i) {
262 const auto sig = uid.signature(i);
263 if (Kleo::isSelfSignature(sig)) {
264 return sig.creationTime();
265 }
266 }
267 return 0;
268}
269
270bool Kleo::userIDsAreEqual(const GpgME::UserID &lhs, const GpgME::UserID &rhs)
271{
272 return (qstrcmp(lhs.parent().primaryFingerprint(), rhs.parent().primaryFingerprint()) == 0 //
273 && qstrcmp(lhs.id(), rhs.id()) == 0 //
274 && creationDate(lhs) == creationDate(rhs));
275}
276
277static inline bool isOpenPGPCertification(const GpgME::UserID::Signature &sig)
278{
279 // certification class is 0x10, ..., 0x13
280 return (sig.certClass() & ~0x03) == 0x10;
281}
282
283static bool isOpenPGPCertificationByUser(const GpgME::UserID::Signature &sig)
284{
285 if (!isOpenPGPCertification(sig)) {
286 return false;
287 }
288 const auto certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID());
289 return certificationKey.ownerTrust() == Key::Ultimate;
290}
291
292bool Kleo::userIDIsCertifiedByUser(const GpgME::UserID &userId)
293{
294 if (userId.parent().protocol() != GpgME::OpenPGP) {
295 qCWarning(LIBKLEO_LOG) << __func__ << "not called with OpenPGP key";
296 return false;
297 }
298 if (userId.numSignatures() == 0) {
299 qCWarning(LIBKLEO_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available";
300 }
301 for (unsigned int i = 0, numSignatures = userId.numSignatures(); i < numSignatures; ++i) {
302 const auto sig = userId.signature(i);
303 if ((sig.status() == UserID::Signature::NoError) && !sig.isBad() && sig.isExportable() && isOpenPGPCertificationByUser(sig)) {
304 return true;
305 }
306 }
307 return false;
308}
QString fromUtf8(QByteArrayView str)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:12 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.