Libkleo

expirychecker.cpp
1/*
2 This file is part of libkleopatra, the KDE keymanagement library
3 SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
4 SPDX-FileCopyrightText: 2021 Sandro Knauß <sknauss@kde.org>
5 SPDX-FileCopyrightText: 2023 g10 Code GmbH
6 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
7
8 Based on kpgp.h
9 Copyright (C) 2001,2002 the KPGP authors
10 See file libkdenetwork/AUTHORS.kpgp for details
11
12 SPDX-License-Identifier: LGPL-2.0-or-later
13*/
14
15#include "expirychecker.h"
16
17#include "debug.h"
18#include "dn.h"
19#include "expirycheckersettings.h"
20
21#include <libkleo/algorithm.h>
22#include <libkleo/keycache.h>
23#include <libkleo_debug.h>
24
25#include <KLocalizedString>
26
27#include <QGpgME/KeyListJob>
28#include <QGpgME/Protocol>
29
30#include <QTimeZone>
31
32#include <gpgme++/keylistresult.h>
33
34#include <set>
35
36#include <cmath>
37#include <ctime>
38
39using namespace Kleo;
40
41class Kleo::ExpiryCheckerPrivate
42{
43 Kleo::ExpiryChecker *q;
44
45public:
46 ExpiryCheckerPrivate(ExpiryChecker *qq, const ExpiryCheckerSettings &settings_)
47 : q{qq}
48 , settings{settings_}
49 {
50 }
51
52 ExpiryChecker::Expiration calculateExpiration(const GpgME::Subkey &subkey) const;
53 ExpiryChecker::Expiration checkForExpiration(const GpgME::Key &key, Kleo::chrono::days threshold, ExpiryChecker::CheckFlags flags) const;
54
55 ExpiryChecker::Result checkKeyNearExpiry(const GpgME::Key &key, ExpiryChecker::CheckFlags flags);
56
57 ExpiryCheckerSettings settings;
58 std::set<QByteArray> alreadyWarnedFingerprints;
59 std::shared_ptr<TimeProvider> timeProvider;
60};
61
62ExpiryChecker::ExpiryChecker(const ExpiryCheckerSettings &settings, QObject *parent)
63 : QObject{parent}
64 , d{new ExpiryCheckerPrivate{this, settings}}
65{
66}
67
68ExpiryChecker::~ExpiryChecker() = default;
69
70ExpiryCheckerSettings ExpiryChecker::settings() const
71{
72 return d->settings;
73}
74
75QString formatOpenPGPMessage(ExpiryChecker::Expiration expiration, ExpiryChecker::CheckFlags flags)
76{
77 const GpgME::Key key = expiration.certificate;
78 const bool isOwnKey = flags & ExpiryChecker::OwnKey;
79 const bool isSigningKey = flags & ExpiryChecker::SigningKey;
80 const auto keyInfo = ki18nc("<b>User ID of key</b> (Key ID key ID of key in hex notation)", "<b>%1</b> (Key ID 0x%2)")
81 .subs(QString::fromUtf8(key.userID(0).id()))
82 .subs(QString::fromLatin1(key.keyID()));
83 if (expiration.status == ExpiryChecker::Expired) {
84 qCDebug(LIBKLEO_LOG) << "Key" << key << "expired" << expiration.duration.count() << "days ago";
85 if (expiration.duration.count() == 0) {
86 KLocalizedString msg;
87 if (isSigningKey) {
88 msg = ki18n("<p>Your OpenPGP signing key</p><p align=center>%1</p><p>expired less than a day ago.</p>");
89 } else if (isOwnKey) {
90 msg = ki18n("<p>Your OpenPGP encryption key</p><p align=center>%1</p><p>expired less than a day ago.</p>");
91 } else {
92 msg = ki18n("<p>The OpenPGP key for</p><p align=center>%1</p><p>expired less than a day ago.</p>");
93 }
94 return msg.subs(keyInfo).toString();
95 }
96 KLocalizedString msg;
97 if (isSigningKey) {
98 msg = ki18np("<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expired yesterday.</p>",
99 "<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expired %1 days ago.</p>");
100 } else if (isOwnKey) {
101 msg = ki18np("<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expired yesterday.</p>",
102 "<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expired %1 days ago.</p>");
103 } else {
104 msg = ki18np("<p>The OpenPGP key for</p><p align=center>%2</p><p>expired yesterday.</p>",
105 "<p>The OpenPGP key for</p><p align=center>%2</p><p>expired %1 days ago.</p>");
106 }
107 return msg.subs(expiration.duration.count()).subs(keyInfo).toString();
108 }
109 qCDebug(LIBKLEO_LOG) << "Key" << key << "expires in" << expiration.duration.count() << "days";
110 if (expiration.duration.count() == 0) {
111 KLocalizedString msg;
112 if (isSigningKey) {
113 msg = ki18n("<p>Your OpenPGP signing key</p><p align=center>%1</p><p>expires today.</p>");
114 } else if (isOwnKey) {
115 msg = ki18n("<p>Your OpenPGP encryption key</p><p align=center>%1</p><p>expires today.</p>");
116 } else {
117 msg = ki18n("<p>The OpenPGP key for</p><p align=center>%1</p><p>expires today.</p>");
118 }
119 return msg.subs(keyInfo).toString();
120 }
121 KLocalizedString msg;
122 if (isSigningKey) {
123 msg = ki18np("<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expires tomorrow.</p>",
124 "<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expires in %1 days.</p>");
125 } else if (isOwnKey) {
126 msg = ki18np("<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expires tomorrow.</p>",
127 "<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expires in %1 days.</p>");
128 } else {
129 msg = ki18np("<p>The OpenPGP key for</p><p align=center>%2</p><p>expires tomorrow.</p>",
130 "<p>The OpenPGP key for</p><p align=center>%2</p><p>expires in %1 days.</p>");
131 }
132 return msg.subs(expiration.duration.count()).subs(keyInfo).toString();
133}
134
135QString formatSMIMEMessage(const GpgME::Key &orig_key, ExpiryChecker::Expiration expiration, ExpiryChecker::CheckFlags flags, bool ca)
136{
137 const GpgME::Key key = expiration.certificate;
138 const bool isOwnKey = flags & ExpiryChecker::OwnKey;
139 const bool isSigningKey = flags & ExpiryChecker::SigningKey;
140 const auto userCert = orig_key.isNull() ? key : orig_key;
141 const auto userCertInfo = ki18nc("<b>User ID of certificate</b> (serial number serial no. of certificate)", "<b>%1</b> (serial number %2)")
142 .subs(Kleo::DN(userCert.userID(0).id()).prettyDN())
143 .subs(QString::fromLatin1(userCert.issuerSerial()));
144 if (expiration.status == ExpiryChecker::Expired) {
145 qCDebug(LIBKLEO_LOG) << "Certificate" << key << "expired" << expiration.duration.count() << "days ago";
146 if (ca) {
147 if (key.isRoot()) {
148 if (expiration.duration.count() == 0) {
149 KLocalizedString msg;
150 if (isSigningKey) {
151 msg = ki18n(
152 "<p>The root certificate</p><p align=center><b>%2</b></p>"
153 "<p>for your S/MIME signing certificate</p><p align=center>%1</p>"
154 "<p>expired less than a day ago.</p>");
155 } else if (isOwnKey) {
156 msg = ki18n(
157 "<p>The root certificate</p><p align=center><b>%2</b></p>"
158 "<p>for your S/MIME encryption certificate</p><p align=center>%1</p>"
159 "<p>expired less than a day ago.</p>");
160 } else {
161 msg = ki18n(
162 "<p>The root certificate</p><p align=center><b>%2</b></p>"
163 "<p>for S/MIME certificate</p><p align=center>%1</p>"
164 "<p>expired less than a day ago.</p>");
165 }
166 return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
167 }
168 KLocalizedString msg;
169 if (isSigningKey) {
170 msg = ki18np(
171 "<p>The root certificate</p><p align=center><b>%3</b></p>"
172 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
173 "<p>expired yesterday.</p>",
174 "<p>The root certificate</p><p align=center><b>%3</b></p>"
175 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
176 "<p>expired %1 days ago.</p>");
177 } else if (isOwnKey) {
178 msg = ki18np(
179 "<p>The root certificate</p><p align=center><b>%3</b></p>"
180 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
181 "<p>expired yesterday.</p>",
182 "<p>The root certificate</p><p align=center><b>%3</b></p>"
183 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
184 "<p>expired %1 days ago.</p>");
185 } else {
186 msg = ki18np(
187 "<p>The root certificate</p><p align=center><b>%3</b></p>"
188 "<p>for S/MIME certificate</p><p align=center>%2</p>"
189 "<p>expired yesterday.</p>",
190 "<p>The root certificate</p><p align=center><b>%3</b></p>"
191 "<p>for S/MIME certificate</p><p align=center>%2</p>"
192 "<p>expired %1 days ago.</p>");
193 }
194 return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
195 } else {
196 if (expiration.duration.count() == 0) {
197 KLocalizedString msg;
198 if (isSigningKey) {
199 msg = ki18n(
200 "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
201 "<p>for your S/MIME signing certificate</p><p align=center>%1</p>"
202 "<p>expired less than a day ago.</p>");
203 } else if (isOwnKey) {
204 msg = ki18n(
205 "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
206 "<p>for your S/MIME encryption certificate</p><p align=center>%1</p>"
207 "<p>expired less than a day ago.</p>");
208 } else {
209 msg = ki18n(
210 "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
211 "<p>for S/MIME certificate</p><p align=center>%1</p>"
212 "<p>expired less than a day ago.</p>");
213 }
214 return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
215 }
216 KLocalizedString msg;
217 if (isSigningKey) {
218 msg = ki18np(
219 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
220 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
221 "<p>expired yesterday.</p>",
222 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
223 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
224 "<p>expired %1 days ago.</p>");
225 } else if (isOwnKey) {
226 msg = ki18np(
227 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
228 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
229 "<p>expired yesterday.</p>",
230 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
231 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
232 "<p>expired %1 days ago.</p>");
233 } else {
234 msg = ki18np(
235 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
236 "<p>for S/MIME certificate</p><p align=center>%2</p>"
237 "<p>expired yesterday.</p>",
238 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
239 "<p>for S/MIME certificate</p><p align=center>%2</p>"
240 "<p>expired %1 days ago.</p>");
241 }
242 return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
243 }
244 } else {
245 if (expiration.duration.count() == 0) {
246 KLocalizedString msg;
247 if (isSigningKey) {
248 msg = ki18n("<p>Your S/MIME signing certificate</p><p align=center>%1</p><p>expired less than a day ago.</p>");
249 } else if (isOwnKey) {
250 msg = ki18n("<p>Your S/MIME encryption certificate</p><p align=center>%1</p><p>expired less than a day ago.</p>");
251 } else {
252 msg = ki18n("<p>The S/MIME certificate for</p><p align=center>%1</p><p>expired less than a day ago.</p>");
253 }
254 return msg.subs(userCertInfo).toString();
255 }
256 KLocalizedString msg;
257 if (isSigningKey) {
258 msg = ki18np("<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expired yesterday.</p>",
259 "<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expired %1 days ago.</p>");
260 } else if (isOwnKey) {
261 msg = ki18np("<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expired yesterday.</p>",
262 "<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expired %1 days ago.</p>");
263 } else {
264 msg = ki18np("<p>The S/MIME certificate for</p><p align=center>%2</p><p>expired yesterday.</p>",
265 "<p>The S/MIME certificate for</p><p align=center>%2</p><p>expired %1 days ago.</p>");
266 }
267 return msg.subs(expiration.duration.count()).subs(userCertInfo).toString();
268 }
269 }
270 qCDebug(LIBKLEO_LOG) << "Certificate" << key << "expires in" << expiration.duration.count() << "days";
271 if (ca) {
272 if (key.isRoot()) {
273 if (expiration.duration.count() == 0) {
274 KLocalizedString msg;
275 if (isSigningKey) {
276 msg = ki18n(
277 "<p>The root certificate</p><p align=center><b>%3</b></p>"
278 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
279 "<p>expires today.</p>");
280 } else if (isOwnKey) {
281 msg = ki18n(
282 "<p>The root certificate</p><p align=center><b>%3</b></p>"
283 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
284 "<p>expires today.</p>");
285 } else {
286 msg = ki18n(
287 "<p>The root certificate</p><p align=center><b>%3</b></p>"
288 "<p>for S/MIME certificate</p><p align=center>%2</p>"
289 "<p>expires today.</p>");
290 }
291 return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
292 }
293 KLocalizedString msg;
294 if (isSigningKey) {
295 msg = ki18np(
296 "<p>The root certificate</p><p align=center><b>%3</b></p>"
297 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
298 "<p>expires tomorrow.</p>",
299 "<p>The root certificate</p><p align=center><b>%3</b></p>"
300 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
301 "<p>expires in %1 days.</p>");
302 } else if (isOwnKey) {
303 msg = ki18np(
304 "<p>The root certificate</p><p align=center><b>%3</b></p>"
305 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
306 "<p>expires tomorrow.</p>",
307 "<p>The root certificate</p><p align=center><b>%3</b></p>"
308 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
309 "<p>expires in %1 days.</p>");
310 } else {
311 msg = ki18np(
312 "<p>The root certificate</p><p align=center><b>%3</b></p>"
313 "<p>for S/MIME certificate</p><p align=center>%2</p>"
314 "<p>expires tomorrow.</p>",
315 "<p>The root certificate</p><p align=center><b>%3</b></p>"
316 "<p>for S/MIME certificate</p><p align=center>%2</p>"
317 "<p>expires in %1 days.</p>");
318 }
319 return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
320 }
321 if (expiration.duration.count() == 0) {
322 KLocalizedString msg;
323 if (isSigningKey) {
324 msg = ki18n(
325 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
326 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
327 "<p>expires today.</p>");
328 } else if (isOwnKey) {
329 msg = ki18n(
330 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
331 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
332 "<p>expires today.</p>");
333 } else {
334 msg = ki18n(
335 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
336 "<p>for S/MIME certificate</p><p align=center>%2</p>"
337 "<p>expires today.</p>");
338 }
339 }
340 KLocalizedString msg;
341 if (isSigningKey) {
342 msg = ki18np(
343 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
344 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
345 "<p>expires tomorrow.</p>",
346 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
347 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
348 "<p>expires in %1 days.</p>");
349 } else if (isOwnKey) {
350 msg = ki18np(
351 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
352 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
353 "<p>expires tomorrow.</p>",
354 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
355 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
356 "<p>expires in %1 days.</p>");
357 } else {
358 msg = ki18np(
359 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
360 "<p>for S/MIME certificate</p><p align=center>%2</p>"
361 "<p>expires tomorrow.</p>",
362 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
363 "<p>for S/MIME certificate</p><p align=center>%2</p>"
364 "<p>expires in %1 days.</p>");
365 }
366 return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
367 }
368 if (expiration.duration.count() == 0) {
369 KLocalizedString msg;
370 if (isSigningKey) {
371 msg = ki18n("<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expires today.</p>");
372 } else if (isOwnKey) {
373 msg = ki18n("<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expires today.</p>");
374 } else {
375 msg = ki18n("<p>The S/MIME certificate for</p><p align=center>%2</p><p>expires today.</p>");
376 }
377 return msg.subs(userCertInfo).toString();
378 }
379 KLocalizedString msg;
380 if (isSigningKey) {
381 msg = ki18np(
382 "<p>Your S/MIME signing certificate</p><p align=center>%2</p>"
383 "<p>expires tomorrow.</p>",
384 "<p>Your S/MIME signing certificate</p><p align=center>%2</p>"
385 "<p>expires in %1 days.</p>");
386 } else if (isOwnKey) {
387 msg = ki18np(
388 "<p>Your S/MIME encryption certificate</p><p align=center>%2</p>"
389 "<p>expires tomorrow.</p>",
390 "<p>Your S/MIME encryption certificate</p><p align=center>%2</p>"
391 "<p>expires in %1 days.</p>");
392 } else {
393 msg = ki18np(
394 "<p>The S/MIME certificate for</p><p align=center>%2</p>"
395 "<p>expires tomorrow.</p>",
396 "<p>The S/MIME certificate for</p><p align=center>%2</p>"
397 "<p>expires in %1 days.</p>");
398 }
399 return msg.subs(expiration.duration.count()).subs(userCertInfo).toString();
400}
401
402static GpgME::Subkey findBestSubkey(const GpgME::Key &key, ExpiryChecker::CheckFlags usageFlags)
403{
404 // find the subkey with the latest expiration date for the given usage flags
405 if (!(usageFlags & ExpiryChecker::UsageMask)) {
406 // return primary key if no specific usage is specified (as for chain certificates)
407 return key.subkey(0);
408 }
409 GpgME::Subkey result;
410 for (unsigned int i = 0; i < key.numSubkeys(); ++i) {
411 const auto subkey = key.subkey(i);
412 if (subkey.isRevoked() || subkey.isInvalid() || subkey.isDisabled()) {
413 // unusable subkey
414 continue;
415 }
416 if (((usageFlags & ExpiryChecker::EncryptionKey) && !subkey.canEncrypt()) //
417 || ((usageFlags & ExpiryChecker::SigningKey) && !subkey.canSign()) //
418 || ((usageFlags & ExpiryChecker::CertificationKey) && !subkey.canCertify())) {
419 // unsuitable subkey for requested usage
420 continue;
421 }
422 if (subkey.neverExpires()) {
423 // stop looking for the best subkey if we found a suitable subkey that doesn't expire;
424 // return the primary key because a non-expiring subkey inherits the primary key's expiration
425 return key.subkey(0);
426 }
427 if (quint32(subkey.expirationTime()) > quint32(result.expirationTime())) {
428 result = subkey;
429 }
430 }
431 return result;
432}
433
434ExpiryChecker::Expiration ExpiryCheckerPrivate::calculateExpiration(const GpgME::Subkey &subkey) const
435{
436 if (subkey.neverExpires()) {
437 return {subkey.parent(), ExpiryChecker::NotNearExpiry, Kleo::chrono::days::zero()};
438 }
439 const qint64 currentTime = timeProvider ? timeProvider->currentTime() : QDateTime::currentSecsSinceEpoch();
440 const auto currentDate = timeProvider ? timeProvider->currentDate() : QDate::currentDate();
441 const auto timeZone = timeProvider ? timeProvider->timeZone() : QTimeZone{QTimeZone::LocalTime};
442 // interpret the expiration time as unsigned 32-bit value if it's negative; gpg also uses uint32 internally
443 const qint64 expirationTime = qint64(subkey.expirationTime() < 0 ? quint32(subkey.expirationTime()) : subkey.expirationTime());
444 const auto expirationDate = QDateTime::fromSecsSinceEpoch(expirationTime, timeZone).date();
445 if (expirationTime <= currentTime) {
446 return {subkey.parent(), ExpiryChecker::Expired, Kleo::chrono::days{expirationDate.daysTo(currentDate)}};
447 } else {
448 return {subkey.parent(), ExpiryChecker::ExpiresSoon, Kleo::chrono::days{currentDate.daysTo(expirationDate)}};
449 }
450}
451
452ExpiryChecker::Expiration ExpiryCheckerPrivate::checkForExpiration(const GpgME::Key &key, //
453 Kleo::chrono::days threshold,
454 ExpiryChecker::CheckFlags usageFlags) const
455{
456 const auto subkey = findBestSubkey(key, usageFlags);
457 if (subkey.isNull()) {
458 return {key, ExpiryChecker::NoSuitableSubkey, {}};
459 }
460 ExpiryChecker::Expiration expiration = calculateExpiration(subkey);
461 if ((expiration.status == ExpiryChecker::ExpiresSoon) && (expiration.duration > threshold)) {
462 // key expires, but not too soon
463 expiration.status = ExpiryChecker::NotNearExpiry;
464 }
465 return expiration;
466}
467
468ExpiryChecker::Result ExpiryCheckerPrivate::checkKeyNearExpiry(const GpgME::Key &orig_key, ExpiryChecker::CheckFlags flags)
469{
470 static const int maximumCertificateChainLength = 100;
471 const bool isOwnKey = flags & ExpiryChecker::OwnKey;
472
473 ExpiryChecker::Result result;
474 result.checkFlags = flags;
475 result.expiration.certificate = orig_key;
476
477 // use vector instead of set because certificate chains are usually very short
478 std::vector<std::string> checkedCertificates;
479 auto key = orig_key;
480 for (int chainCount = 0; chainCount < maximumCertificateChainLength; ++chainCount) {
481 checkedCertificates.push_back(key.primaryFingerprint());
482
483 const GpgME::Subkey subkey = key.subkey(0);
484
485 const bool newMessage = !alreadyWarnedFingerprints.count(subkey.fingerprint());
486
487 const auto threshold = chainCount > 0 //
488 ? (key.isRoot() ? settings.rootCertThreshold() : settings.chainCertThreshold()) //
489 : (isOwnKey ? settings.ownKeyThreshold() : settings.otherKeyThreshold());
490 const auto usageFlags = (chainCount == 0) ? (flags & ExpiryChecker::UsageMask) : ExpiryChecker::CheckFlags{};
491 const auto expiration = checkForExpiration(key, threshold, usageFlags);
492 if (chainCount == 0) {
493 result.expiration = expiration;
494 } else if (expiration.status != ExpiryChecker::NotNearExpiry) {
495 result.chainExpiration.push_back(expiration);
496 }
497 if (expiration.status == ExpiryChecker::Expired) {
498 const QString msg = key.protocol() == GpgME::OpenPGP //
499 ? formatOpenPGPMessage(expiration, flags)
500 : formatSMIMEMessage(orig_key, expiration, flags, chainCount > 0);
501 alreadyWarnedFingerprints.insert(subkey.fingerprint());
502 Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyExpired : ExpiryChecker::OtherKeyExpired, newMessage);
503 } else if (expiration.status == ExpiryChecker::ExpiresSoon) {
504 const QString msg = key.protocol() == GpgME::OpenPGP //
505 ? formatOpenPGPMessage(expiration, flags)
506 : formatSMIMEMessage(orig_key, expiration, flags, chainCount > 0);
507 alreadyWarnedFingerprints.insert(subkey.fingerprint());
508 Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyNearExpiry : ExpiryChecker::OtherKeyNearExpiry, newMessage);
509 } else if (expiration.status == ExpiryChecker::NoSuitableSubkey) {
510 break;
511 }
512 if (!(flags & ExpiryChecker::CheckChain) || key.isRoot() || (key.protocol() != GpgME::CMS)) {
513 break;
514 }
515 const auto keys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption);
516 if (keys.empty()) {
517 break;
518 }
519 key = keys.front();
520 if (Kleo::contains(checkedCertificates, key.primaryFingerprint())) {
521 break; // this certificate was already checked (looks like a circle in the chain)
522 }
523 }
524 return result;
525}
526
527ExpiryChecker::Result ExpiryChecker::checkKey(const GpgME::Key &key, CheckFlags flags) const
528{
529 if (key.isNull()) {
530 qWarning(LIBKLEO_LOG) << __func__ << "called with null key";
531 return {flags, {key, InvalidKey, {}}, {}};
532 }
533 if (!(flags & UsageMask)) {
534 qWarning(LIBKLEO_LOG) << __func__ << "called with invalid flags:" << flags;
535 return {flags, {key, InvalidCheckFlags, {}}, {}};
536 }
537 return d->checkKeyNearExpiry(key, flags);
538}
539
540void ExpiryChecker::setTimeProviderForTest(const std::shared_ptr<TimeProvider> &timeProvider)
541{
542 d->timeProvider = timeProvider;
543}
544
545#include "moc_expirychecker.cpp"
QString toString() const
KLocalizedString subs(const KLocalizedString &a, int fieldWidth=0, QChar fillChar=QLatin1Char(' ')) const
KLocalizedString KI18N_EXPORT ki18np(const char *singular, const char *plural)
KLocalizedString KI18N_EXPORT ki18n(const char *text)
KLocalizedString KI18N_EXPORT ki18nc(const char *context, const char *text)
QDate currentDate()
qint64 daysTo(QDate d) const const
qint64 currentSecsSinceEpoch()
QDate date() const const
QDateTime fromSecsSinceEpoch(qint64 secs)
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QString & insert(qsizetype position, QChar ch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:50:11 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.