15#include "expirychecker.h"
19#include "expirycheckersettings.h"
21#include <libkleo/algorithm.h>
22#include <libkleo/keycache.h>
23#include <libkleo_debug.h>
25#include <KLocalizedString>
27#include <QGpgME/KeyListJob>
28#include <QGpgME/Protocol>
30#include <gpgme++/keylistresult.h>
39class Kleo::ExpiryCheckerPrivate
41 Kleo::ExpiryChecker *q;
44 ExpiryCheckerPrivate(ExpiryChecker *qq,
const ExpiryCheckerSettings &settings_)
50 ExpiryChecker::Expiration calculateExpiration(
const GpgME::Subkey &subkey)
const;
51 ExpiryChecker::Expiration checkForExpiration(
const GpgME::Key &key, Kleo::chrono::days threshold,
ExpiryChecker::CheckFlags flags)
const;
55 ExpiryCheckerSettings settings;
56 std::set<QByteArray> alreadyWarnedFingerprints;
57 std::shared_ptr<TimeProvider> timeProvider;
60ExpiryChecker::ExpiryChecker(
const ExpiryCheckerSettings &settings,
QObject *parent)
62 , d{new ExpiryCheckerPrivate{this, settings}}
66ExpiryChecker::~ExpiryChecker() =
default;
68ExpiryCheckerSettings ExpiryChecker::settings()
const
75 const GpgME::Key key = expiration.certificate;
76 const bool isOwnKey = flags & ExpiryChecker::OwnKey;
77 const bool isSigningKey = flags & ExpiryChecker::SigningKey;
78 const auto keyInfo =
ki18nc(
"<b>User ID of key</b> (KeyID key ID of key in hex notation)",
"<b>%1</b> (KeyID 0x%2)")
81 if (expiration.status == ExpiryChecker::Expired) {
82 qCDebug(LIBKLEO_LOG) <<
"Key" << key <<
"expired" << expiration.duration.count() <<
"days ago";
83 if (expiration.duration.count() == 0) {
86 msg =
ki18n(
"<p>Your OpenPGP signing key</p><p align=center>%1</p><p>expired less than a day ago.</p>");
87 }
else if (isOwnKey) {
88 msg =
ki18n(
"<p>Your OpenPGP encryption key</p><p align=center>%1</p><p>expired less than a day ago.</p>");
90 msg =
ki18n(
"<p>The OpenPGP key for</p><p align=center>%1</p><p>expired less than a day ago.</p>");
96 msg =
ki18np(
"<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expired yesterday.</p>",
97 "<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expired %1 days ago.</p>");
98 }
else if (isOwnKey) {
99 msg =
ki18np(
"<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expired yesterday.</p>",
100 "<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expired %1 days ago.</p>");
102 msg =
ki18np(
"<p>The OpenPGP key for</p><p align=center>%2</p><p>expired yesterday.</p>",
103 "<p>The OpenPGP key for</p><p align=center>%2</p><p>expired %1 days ago.</p>");
107 qCDebug(LIBKLEO_LOG) <<
"Key" << key <<
"expires in" << expiration.duration.count() <<
"days";
108 if (expiration.duration.count() == 0) {
111 msg =
ki18n(
"<p>Your OpenPGP signing key</p><p align=center>%1</p><p>expires today.</p>");
112 }
else if (isOwnKey) {
113 msg =
ki18n(
"<p>Your OpenPGP encryption key</p><p align=center>%1</p><p>expires today.</p>");
115 msg =
ki18n(
"<p>The OpenPGP key for</p><p align=center>%1</p><p>expires today.</p>");
121 msg =
ki18np(
"<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expires tomorrow.</p>",
122 "<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expires in %1 days.</p>");
123 }
else if (isOwnKey) {
124 msg =
ki18np(
"<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expires tomorrow.</p>",
125 "<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expires in %1 days.</p>");
127 msg =
ki18np(
"<p>The OpenPGP key for</p><p align=center>%2</p><p>expires tomorrow.</p>",
128 "<p>The OpenPGP key for</p><p align=center>%2</p><p>expires in %1 days.</p>");
135 const GpgME::Key key = expiration.certificate;
136 const bool isOwnKey = flags & ExpiryChecker::OwnKey;
137 const bool isSigningKey = flags & ExpiryChecker::SigningKey;
138 const auto userCert = orig_key.isNull() ? key : orig_key;
139 const auto userCertInfo =
ki18nc(
"<b>User ID of certificate</b> (serial number serial no. of certificate)",
"<b>%1</b> (serial number %2)")
142 if (expiration.status == ExpiryChecker::Expired) {
143 qCDebug(LIBKLEO_LOG) <<
"Certificate" << key <<
"expired" << expiration.duration.count() <<
"days ago";
146 if (expiration.duration.count() == 0) {
150 "<p>The root certificate</p><p align=center><b>%2</b></p>"
151 "<p>for your S/MIME signing certificate</p><p align=center>%1</p>"
152 "<p>expired less than a day ago.</p>");
153 }
else if (isOwnKey) {
155 "<p>The root certificate</p><p align=center><b>%2</b></p>"
156 "<p>for your S/MIME encryption certificate</p><p align=center>%1</p>"
157 "<p>expired less than a day ago.</p>");
160 "<p>The root certificate</p><p align=center><b>%2</b></p>"
161 "<p>for S/MIME certificate</p><p align=center>%1</p>"
162 "<p>expired less than a day ago.</p>");
169 "<p>The root certificate</p><p align=center><b>%3</b></p>"
170 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
171 "<p>expired yesterday.</p>",
172 "<p>The root certificate</p><p align=center><b>%3</b></p>"
173 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
174 "<p>expired %1 days ago.</p>");
175 }
else if (isOwnKey) {
177 "<p>The root certificate</p><p align=center><b>%3</b></p>"
178 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
179 "<p>expired yesterday.</p>",
180 "<p>The root certificate</p><p align=center><b>%3</b></p>"
181 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
182 "<p>expired %1 days ago.</p>");
185 "<p>The root certificate</p><p align=center><b>%3</b></p>"
186 "<p>for S/MIME certificate</p><p align=center>%2</p>"
187 "<p>expired yesterday.</p>",
188 "<p>The root certificate</p><p align=center><b>%3</b></p>"
189 "<p>for S/MIME certificate</p><p align=center>%2</p>"
190 "<p>expired %1 days ago.</p>");
194 if (expiration.duration.count() == 0) {
198 "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
199 "<p>for your S/MIME signing certificate</p><p align=center>%1</p>"
200 "<p>expired less than a day ago.</p>");
201 }
else if (isOwnKey) {
203 "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
204 "<p>for your S/MIME encryption certificate</p><p align=center>%1</p>"
205 "<p>expired less than a day ago.</p>");
208 "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
209 "<p>for S/MIME certificate</p><p align=center>%1</p>"
210 "<p>expired less than a day ago.</p>");
217 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
218 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
219 "<p>expired yesterday.</p>",
220 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
221 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
222 "<p>expired %1 days ago.</p>");
223 }
else if (isOwnKey) {
225 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
226 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
227 "<p>expired yesterday.</p>",
228 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
229 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
230 "<p>expired %1 days ago.</p>");
233 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
234 "<p>for S/MIME certificate</p><p align=center>%2</p>"
235 "<p>expired yesterday.</p>",
236 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
237 "<p>for S/MIME certificate</p><p align=center>%2</p>"
238 "<p>expired %1 days ago.</p>");
243 if (expiration.duration.count() == 0) {
246 msg =
ki18n(
"<p>Your S/MIME signing certificate</p><p align=center>%1</p><p>expired less than a day ago.</p>");
247 }
else if (isOwnKey) {
248 msg =
ki18n(
"<p>Your S/MIME encryption certificate</p><p align=center>%1</p><p>expired less than a day ago.</p>");
250 msg =
ki18n(
"<p>The S/MIME certificate for</p><p align=center>%1</p><p>expired less than a day ago.</p>");
256 msg =
ki18np(
"<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expired yesterday.</p>",
257 "<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expired %1 days ago.</p>");
258 }
else if (isOwnKey) {
259 msg =
ki18np(
"<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expired yesterday.</p>",
260 "<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expired %1 days ago.</p>");
262 msg =
ki18np(
"<p>The S/MIME certificate for</p><p align=center>%2</p><p>expired yesterday.</p>",
263 "<p>The S/MIME certificate for</p><p align=center>%2</p><p>expired %1 days ago.</p>");
268 qCDebug(LIBKLEO_LOG) <<
"Certificate" << key <<
"expires in" << expiration.duration.count() <<
"days";
271 if (expiration.duration.count() == 0) {
275 "<p>The root certificate</p><p align=center><b>%3</b></p>"
276 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
277 "<p>expires today.</p>");
278 }
else if (isOwnKey) {
280 "<p>The root certificate</p><p align=center><b>%3</b></p>"
281 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
282 "<p>expires today.</p>");
285 "<p>The root certificate</p><p align=center><b>%3</b></p>"
286 "<p>for S/MIME certificate</p><p align=center>%2</p>"
287 "<p>expires today.</p>");
294 "<p>The root certificate</p><p align=center><b>%3</b></p>"
295 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
296 "<p>expires tomorrow.</p>",
297 "<p>The root certificate</p><p align=center><b>%3</b></p>"
298 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
299 "<p>expires in %1 days.</p>");
300 }
else if (isOwnKey) {
302 "<p>The root certificate</p><p align=center><b>%3</b></p>"
303 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
304 "<p>expires tomorrow.</p>",
305 "<p>The root certificate</p><p align=center><b>%3</b></p>"
306 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
307 "<p>expires in %1 days.</p>");
310 "<p>The root certificate</p><p align=center><b>%3</b></p>"
311 "<p>for S/MIME certificate</p><p align=center>%2</p>"
312 "<p>expires tomorrow.</p>",
313 "<p>The root certificate</p><p align=center><b>%3</b></p>"
314 "<p>for S/MIME certificate</p><p align=center>%2</p>"
315 "<p>expires in %1 days.</p>");
319 if (expiration.duration.count() == 0) {
323 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
324 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
325 "<p>expires today.</p>");
326 }
else if (isOwnKey) {
328 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
329 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
330 "<p>expires today.</p>");
333 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
334 "<p>for S/MIME certificate</p><p align=center>%2</p>"
335 "<p>expires today.</p>");
341 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
342 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
343 "<p>expires tomorrow.</p>",
344 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
345 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
346 "<p>expires in %1 days.</p>");
347 }
else if (isOwnKey) {
349 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
350 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
351 "<p>expires tomorrow.</p>",
352 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
353 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
354 "<p>expires in %1 days.</p>");
357 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
358 "<p>for S/MIME certificate</p><p align=center>%2</p>"
359 "<p>expires tomorrow.</p>",
360 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
361 "<p>for S/MIME certificate</p><p align=center>%2</p>"
362 "<p>expires in %1 days.</p>");
366 if (expiration.duration.count() == 0) {
369 msg =
ki18n(
"<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expires today.</p>");
370 }
else if (isOwnKey) {
371 msg =
ki18n(
"<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expires today.</p>");
373 msg =
ki18n(
"<p>The S/MIME certificate for</p><p align=center>%2</p><p>expires today.</p>");
380 "<p>Your S/MIME signing certificate</p><p align=center>%2</p>"
381 "<p>expires tomorrow.</p>",
382 "<p>Your S/MIME signing certificate</p><p align=center>%2</p>"
383 "<p>expires in %1 days.</p>");
384 }
else if (isOwnKey) {
386 "<p>Your S/MIME encryption certificate</p><p align=center>%2</p>"
387 "<p>expires tomorrow.</p>",
388 "<p>Your S/MIME encryption certificate</p><p align=center>%2</p>"
389 "<p>expires in %1 days.</p>");
392 "<p>The S/MIME certificate for</p><p align=center>%2</p>"
393 "<p>expires tomorrow.</p>",
394 "<p>The S/MIME certificate for</p><p align=center>%2</p>"
395 "<p>expires in %1 days.</p>");
403 if (!(usageFlags & ExpiryChecker::UsageMask)) {
405 return key.subkey(0);
407 GpgME::Subkey result;
408 for (
unsigned int i = 0; i < key.numSubkeys(); ++i) {
409 const auto subkey = key.subkey(i);
410 if (subkey.isRevoked() || subkey.isInvalid() || subkey.isDisabled()) {
414 if (((usageFlags & ExpiryChecker::EncryptionKey) && !subkey.canEncrypt())
415 || ((usageFlags & ExpiryChecker::SigningKey) && !subkey.canSign())
416 || ((usageFlags & ExpiryChecker::CertificationKey) && !subkey.canCertify())) {
420 if (subkey.neverExpires()) {
423 return key.subkey(0);
425 if (quint32(subkey.expirationTime()) > quint32(result.expirationTime())) {
432ExpiryChecker::Expiration ExpiryCheckerPrivate::calculateExpiration(
const GpgME::Subkey &subkey)
const
434 if (subkey.neverExpires()) {
435 return {subkey.parent(), ExpiryChecker::NotNearExpiry, Kleo::chrono::days::zero()};
437 const time_t currentTime = timeProvider ? timeProvider->currentTime() : std::time(
nullptr);
438 const auto currentDate = timeProvider ? timeProvider->currentDate() :
QDate::currentDate();
439 const auto timeSpec = timeProvider ? timeProvider->timeSpec() :
Qt::LocalTime;
440 const time_t expirationTime = subkey.expirationTime();
443 if (std::difftime(expirationTime, currentTime) <= 0) {
444 return {subkey.parent(), ExpiryChecker::Expired, Kleo::chrono::days{expirationDate.
daysTo(currentDate)}};
446 return {subkey.parent(), ExpiryChecker::ExpiresSoon, Kleo::chrono::days{currentDate.daysTo(expirationDate)}};
450ExpiryChecker::Expiration ExpiryCheckerPrivate::checkForExpiration(
const GpgME::Key &key,
451 Kleo::chrono::days threshold,
454 const auto subkey = findBestSubkey(key, usageFlags);
455 if (subkey.isNull()) {
456 return {key, ExpiryChecker::NoSuitableSubkey, {}};
458 ExpiryChecker::Expiration expiration = calculateExpiration(subkey);
459 if ((expiration.status == ExpiryChecker::ExpiresSoon) && (expiration.duration > threshold)) {
461 expiration.status = ExpiryChecker::NotNearExpiry;
466ExpiryChecker::Result ExpiryCheckerPrivate::checkKeyNearExpiry(
const GpgME::Key &orig_key,
ExpiryChecker::CheckFlags flags)
468 static const int maximumCertificateChainLength = 100;
469 const bool isOwnKey = flags & ExpiryChecker::OwnKey;
471 ExpiryChecker::Result result;
472 result.checkFlags = flags;
473 result.expiration.certificate = orig_key;
476 std::vector<std::string> checkedCertificates;
478 for (
int chainCount = 0; chainCount < maximumCertificateChainLength; ++chainCount) {
479 checkedCertificates.push_back(key.primaryFingerprint());
481 const GpgME::Subkey subkey = key.subkey(0);
483 const bool newMessage = !alreadyWarnedFingerprints.count(subkey.fingerprint());
485 const auto threshold = chainCount > 0
486 ? (key.isRoot() ? settings.rootCertThreshold() : settings.chainCertThreshold())
487 : (isOwnKey ? settings.ownKeyThreshold() : settings.otherKeyThreshold());
489 const auto expiration = checkForExpiration(key, threshold, usageFlags);
490 if (chainCount == 0) {
491 result.expiration = expiration;
492 }
else if (expiration.status != ExpiryChecker::NotNearExpiry) {
493 result.chainExpiration.push_back(expiration);
495 if (expiration.status == ExpiryChecker::Expired) {
496 const QString msg = key.protocol() == GpgME::OpenPGP
497 ? formatOpenPGPMessage(expiration, flags)
498 : formatSMIMEMessage(orig_key, expiration, flags, chainCount > 0);
499 alreadyWarnedFingerprints.insert(subkey.fingerprint());
500 Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyExpired : ExpiryChecker::OtherKeyExpired, newMessage);
501 }
else if (expiration.status == ExpiryChecker::ExpiresSoon) {
502 const QString msg = key.protocol() == GpgME::OpenPGP
503 ? formatOpenPGPMessage(expiration, flags)
504 : formatSMIMEMessage(orig_key, expiration, flags, chainCount > 0);
505 alreadyWarnedFingerprints.insert(subkey.fingerprint());
506 Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyNearExpiry : ExpiryChecker::OtherKeyNearExpiry, newMessage);
507 }
else if (expiration.status == ExpiryChecker::NoSuitableSubkey) {
510 if (!(flags & ExpiryChecker::CheckChain) || key.isRoot() || (key.protocol() != GpgME::CMS)) {
513 const auto keys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption);
518 if (Kleo::contains(checkedCertificates, key.primaryFingerprint())) {
525ExpiryChecker::Result ExpiryChecker::checkKey(
const GpgME::Key &key, CheckFlags flags)
const
528 qWarning(LIBKLEO_LOG) << __func__ <<
"called with null key";
529 return {flags, {key, InvalidKey, {}}, {}};
531 if (!(flags & UsageMask)) {
532 qWarning(LIBKLEO_LOG) << __func__ <<
"called with invalid flags:" << flags;
533 return {flags, {key, InvalidCheckFlags, {}}, {}};
535 return d->checkKeyNearExpiry(key, flags);
538void ExpiryChecker::setTimeProviderForTest(
const std::shared_ptr<TimeProvider> &timeProvider)
540 d->timeProvider = timeProvider;
543#include "moc_expirychecker.cpp"
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)
qint64 daysTo(QDate d) const const
QDateTime fromSecsSinceEpoch(qint64 secs)
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)