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 
28 using namespace Kleo;
29 using namespace GpgME;
30 
31 namespace
32 {
33 bool 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 
41 auto _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 
52 std::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 
63 std::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 
74 bool 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 
80 GpgME::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 
89 GpgME::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 
98 bool Kleo::allUserIDsHaveFullValidity(const GpgME::Key &key)
99 {
100  return minimalValidityOfNotRevokedUserIDs(key) >= UserID::Full;
101 }
102 
103 namespace
104 {
105 bool 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 
117 bool 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 
125 bool Kleo::isSelfSignature(const GpgME::UserID::Signature &signature)
126 {
127  return !qstrcmp(signature.parent().parent().keyID(), signature.signerKeyID());
128 }
129 
130 bool 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 
144 bool 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 
158 bool Kleo::canCreateCertifications(const GpgME::Key &key)
159 {
160  return Kleo::keyHasCertify(key) && canBeUsedForSecretKeyOperations(key);
161 }
162 
163 bool Kleo::canBeCertified(const GpgME::Key &key)
164 {
165  return key.protocol() == GpgME::OpenPGP //
166  && !key.isBad() //
167  && hasValidUserID(key);
168 }
169 
170 namespace
171 {
172 static 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 
184 bool 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 
191 bool 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 
198 bool Kleo::canBeUsedForSecretKeyOperations(const GpgME::Key &key)
199 {
200  return subkeyHasSecret(key.subkey(0));
201 }
202 
203 bool Kleo::canRevokeUserID(const GpgME::UserID &userId)
204 {
205  return (!userId.isNull() //
206  && userId.parent().protocol() == GpgME::OpenPGP //
207  && !isLastValidUserID(userId));
208 }
209 
210 bool Kleo::isSecretKeyStoredInKeyRing(const GpgME::Key &key)
211 {
212  return key.subkey(0).isSecret() && !key.subkey(0).isCardKey();
213 }
214 
215 bool 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 
223 Kleo::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 
243 bool 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 
253 bool Kleo::userIDBelongsToKey(const GpgME::UserID &userID, const GpgME::Key &key)
254 {
255  return !qstricmp(userID.parent().primaryFingerprint(), key.primaryFingerprint());
256 }
257 
258 static 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 
270 bool 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 
277 static inline bool isOpenPGPCertification(const GpgME::UserID::Signature &sig)
278 {
279  // certification class is 0x10, ..., 0x13
280  return (sig.certClass() & ~0x03) == 0x10;
281 }
282 
283 static 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 
292 bool 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(const char *str, int size)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 03:56:14 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.