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>
32#include <gpgme++/keylistresult.h>
41class Kleo::ExpiryCheckerPrivate
43 Kleo::ExpiryChecker *q;
46 ExpiryCheckerPrivate(ExpiryChecker *qq,
const ExpiryCheckerSettings &settings_)
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;
57 ExpiryCheckerSettings settings;
58 std::set<QByteArray> alreadyWarnedFingerprints;
59 std::shared_ptr<TimeProvider> timeProvider;
62ExpiryChecker::ExpiryChecker(
const ExpiryCheckerSettings &settings,
QObject *parent)
64 , d{new ExpiryCheckerPrivate{this, settings}}
68ExpiryChecker::~ExpiryChecker() =
default;
70ExpiryCheckerSettings ExpiryChecker::settings()
const
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)")
83 if (expiration.status == ExpiryChecker::Expired) {
84 qCDebug(LIBKLEO_LOG) <<
"Key" << key <<
"expired" << expiration.duration.count() <<
"days ago";
85 if (expiration.duration.count() == 0) {
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>");
92 msg =
ki18n(
"<p>The OpenPGP key for</p><p align=center>%1</p><p>expired less than a day ago.</p>");
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>");
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>");
109 qCDebug(LIBKLEO_LOG) <<
"Key" << key <<
"expires in" << expiration.duration.count() <<
"days";
110 if (expiration.duration.count() == 0) {
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>");
117 msg =
ki18n(
"<p>The OpenPGP key for</p><p align=center>%1</p><p>expires today.</p>");
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>");
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>");
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)")
144 if (expiration.status == ExpiryChecker::Expired) {
145 qCDebug(LIBKLEO_LOG) <<
"Certificate" << key <<
"expired" << expiration.duration.count() <<
"days ago";
148 if (expiration.duration.count() == 0) {
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) {
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>");
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>");
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) {
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>");
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>");
196 if (expiration.duration.count() == 0) {
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) {
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>");
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>");
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) {
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>");
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>");
245 if (expiration.duration.count() == 0) {
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>");
252 msg =
ki18n(
"<p>The S/MIME certificate for</p><p align=center>%1</p><p>expired less than a day ago.</p>");
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>");
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>");
270 qCDebug(LIBKLEO_LOG) <<
"Certificate" << key <<
"expires in" << expiration.duration.count() <<
"days";
273 if (expiration.duration.count() == 0) {
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) {
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>");
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>");
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) {
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>");
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>");
321 if (expiration.duration.count() == 0) {
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) {
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>");
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>");
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) {
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>");
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>");
368 if (expiration.duration.count() == 0) {
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>");
375 msg =
ki18n(
"<p>The S/MIME certificate for</p><p align=center>%2</p><p>expires today.</p>");
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) {
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>");
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>");
405 if (!(usageFlags & ExpiryChecker::UsageMask)) {
407 return key.subkey(0);
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()) {
416 if (((usageFlags & ExpiryChecker::EncryptionKey) && !subkey.canEncrypt())
417 || ((usageFlags & ExpiryChecker::SigningKey) && !subkey.canSign())
418 || ((usageFlags & ExpiryChecker::CertificationKey) && !subkey.canCertify())) {
422 if (subkey.neverExpires()) {
425 return key.subkey(0);
427 if (quint32(subkey.expirationTime()) > quint32(result.expirationTime())) {
434ExpiryChecker::Expiration ExpiryCheckerPrivate::calculateExpiration(
const GpgME::Subkey &subkey)
const
436 if (subkey.neverExpires()) {
437 return {subkey.parent(), ExpiryChecker::NotNearExpiry, Kleo::chrono::days::zero()};
440 const auto currentDate = timeProvider ? timeProvider->currentDate() :
QDate::currentDate();
443 const qint64 expirationTime = qint64(subkey.expirationTime() < 0 ? quint32(subkey.expirationTime()) : subkey.expirationTime());
445 if (expirationTime <= currentTime) {
446 return {subkey.parent(), ExpiryChecker::Expired, Kleo::chrono::days{expirationDate.
daysTo(currentDate)}};
448 return {subkey.parent(), ExpiryChecker::ExpiresSoon, Kleo::chrono::days{currentDate.daysTo(expirationDate)}};
452ExpiryChecker::Expiration ExpiryCheckerPrivate::checkForExpiration(
const GpgME::Key &key,
453 Kleo::chrono::days threshold,
456 const auto subkey = findBestSubkey(key, usageFlags);
457 if (subkey.isNull()) {
458 return {key, ExpiryChecker::NoSuitableSubkey, {}};
460 ExpiryChecker::Expiration expiration = calculateExpiration(subkey);
461 if ((expiration.status == ExpiryChecker::ExpiresSoon) && (expiration.duration > threshold)) {
463 expiration.status = ExpiryChecker::NotNearExpiry;
468ExpiryChecker::Result ExpiryCheckerPrivate::checkKeyNearExpiry(
const GpgME::Key &orig_key,
ExpiryChecker::CheckFlags flags)
470 static const int maximumCertificateChainLength = 100;
471 const bool isOwnKey = flags & ExpiryChecker::OwnKey;
473 ExpiryChecker::Result result;
474 result.checkFlags = flags;
475 result.expiration.certificate = orig_key;
478 std::vector<std::string> checkedCertificates;
480 for (
int chainCount = 0; chainCount < maximumCertificateChainLength; ++chainCount) {
481 checkedCertificates.push_back(key.primaryFingerprint());
483 const GpgME::Subkey subkey = key.subkey(0);
485 const bool newMessage = !alreadyWarnedFingerprints.count(subkey.fingerprint());
487 const auto threshold = chainCount > 0
488 ? (key.isRoot() ? settings.rootCertThreshold() : settings.chainCertThreshold())
489 : (isOwnKey ? settings.ownKeyThreshold() : settings.otherKeyThreshold());
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);
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) {
512 if (!(flags & ExpiryChecker::CheckChain) || key.isRoot() || (key.protocol() != GpgME::CMS)) {
515 const auto keys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption);
520 if (Kleo::contains(checkedCertificates, key.primaryFingerprint())) {
527ExpiryChecker::Result ExpiryChecker::checkKey(
const GpgME::Key &key, CheckFlags flags)
const
530 qWarning(LIBKLEO_LOG) << __func__ <<
"called with null key";
531 return {flags, {key, InvalidKey, {}}, {}};
533 if (!(flags & UsageMask)) {
534 qWarning(LIBKLEO_LOG) << __func__ <<
"called with invalid flags:" << flags;
535 return {flags, {key, InvalidCheckFlags, {}}, {}};
537 return d->checkKeyNearExpiry(key, flags);
540void ExpiryChecker::setTimeProviderForTest(
const std::shared_ptr<TimeProvider> &timeProvider)
542 d->timeProvider = timeProvider;
545#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
qint64 currentSecsSinceEpoch()
QDateTime fromSecsSinceEpoch(qint64 secs)
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)