File indexing completed on 2024-11-17 04:50:26

0001 /*
0002     This file is part of libkleopatra, the KDE keymanagement library
0003     SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
0004     SPDX-FileCopyrightText: 2021 Sandro Knauß <sknauss@kde.org>
0005     SPDX-FileCopyrightText: 2023 g10 Code GmbH
0006     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0007 
0008     Based on kpgp.h
0009     Copyright (C) 2001,2002 the KPGP authors
0010     See file libkdenetwork/AUTHORS.kpgp for details
0011 
0012     SPDX-License-Identifier: LGPL-2.0-or-later
0013 */
0014 
0015 #include "expirychecker.h"
0016 
0017 #include "debug.h"
0018 #include "dn.h"
0019 #include "expirycheckersettings.h"
0020 
0021 #include <libkleo/algorithm.h>
0022 #include <libkleo/keycache.h>
0023 #include <libkleo_debug.h>
0024 
0025 #include <KLocalizedString>
0026 
0027 #include <QGpgME/KeyListJob>
0028 #include <QGpgME/Protocol>
0029 
0030 #include <gpgme++/keylistresult.h>
0031 
0032 #include <set>
0033 
0034 #include <cmath>
0035 #include <ctime>
0036 
0037 using namespace Kleo;
0038 
0039 class Kleo::ExpiryCheckerPrivate
0040 {
0041     Kleo::ExpiryChecker *q;
0042 
0043 public:
0044     ExpiryCheckerPrivate(ExpiryChecker *qq, const ExpiryCheckerSettings &settings_)
0045         : q{qq}
0046         , settings{settings_}
0047     {
0048     }
0049 
0050     ExpiryChecker::Expiration calculateExpiration(const GpgME::Subkey &subkey) const;
0051     ExpiryChecker::Expiration checkForExpiration(const GpgME::Key &key, Kleo::chrono::days threshold, ExpiryChecker::CheckFlags flags) const;
0052 
0053     ExpiryChecker::Result checkKeyNearExpiry(const GpgME::Key &key, ExpiryChecker::CheckFlags flags);
0054 
0055     ExpiryCheckerSettings settings;
0056     std::set<QByteArray> alreadyWarnedFingerprints;
0057     std::shared_ptr<TimeProvider> timeProvider;
0058 };
0059 
0060 ExpiryChecker::ExpiryChecker(const ExpiryCheckerSettings &settings, QObject *parent)
0061     : QObject{parent}
0062     , d{new ExpiryCheckerPrivate{this, settings}}
0063 {
0064 }
0065 
0066 ExpiryChecker::~ExpiryChecker() = default;
0067 
0068 ExpiryCheckerSettings ExpiryChecker::settings() const
0069 {
0070     return d->settings;
0071 }
0072 
0073 QString formatOpenPGPMessage(ExpiryChecker::Expiration expiration, ExpiryChecker::CheckFlags flags)
0074 {
0075     const GpgME::Key key = expiration.certificate;
0076     const bool isOwnKey = flags & ExpiryChecker::OwnKey;
0077     const bool isSigningKey = flags & ExpiryChecker::SigningKey;
0078     const auto keyInfo = ki18nc("<b>User ID of key</b> (KeyID key ID of key in hex notation)", "<b>%1</b> (KeyID 0x%2)")
0079                              .subs(QString::fromUtf8(key.userID(0).id()))
0080                              .subs(QString::fromLatin1(key.keyID()));
0081     if (expiration.status == ExpiryChecker::Expired) {
0082         qCDebug(LIBKLEO_LOG) << "Key" << key << "expired" << expiration.duration.count() << "days ago";
0083         if (expiration.duration.count() == 0) {
0084             KLocalizedString msg;
0085             if (isSigningKey) {
0086                 msg = ki18n("<p>Your OpenPGP signing key</p><p align=center>%1</p><p>expired less than a day ago.</p>");
0087             } else if (isOwnKey) {
0088                 msg = ki18n("<p>Your OpenPGP encryption key</p><p align=center>%1</p><p>expired less than a day ago.</p>");
0089             } else {
0090                 msg = ki18n("<p>The OpenPGP key for</p><p align=center>%1</p><p>expired less than a day ago.</p>");
0091             }
0092             return msg.subs(keyInfo).toString();
0093         }
0094         KLocalizedString msg;
0095         if (isSigningKey) {
0096             msg = ki18np("<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expired yesterday.</p>",
0097                          "<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expired %1 days ago.</p>");
0098         } else if (isOwnKey) {
0099             msg = ki18np("<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expired yesterday.</p>",
0100                          "<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expired %1 days ago.</p>");
0101         } else {
0102             msg = ki18np("<p>The OpenPGP key for</p><p align=center>%2</p><p>expired yesterday.</p>",
0103                          "<p>The OpenPGP key for</p><p align=center>%2</p><p>expired %1 days ago.</p>");
0104         }
0105         return msg.subs(expiration.duration.count()).subs(keyInfo).toString();
0106     }
0107     qCDebug(LIBKLEO_LOG) << "Key" << key << "expires in" << expiration.duration.count() << "days";
0108     if (expiration.duration.count() == 0) {
0109         KLocalizedString msg;
0110         if (isSigningKey) {
0111             msg = ki18n("<p>Your OpenPGP signing key</p><p align=center>%1</p><p>expires today.</p>");
0112         } else if (isOwnKey) {
0113             msg = ki18n("<p>Your OpenPGP encryption key</p><p align=center>%1</p><p>expires today.</p>");
0114         } else {
0115             msg = ki18n("<p>The OpenPGP key for</p><p align=center>%1</p><p>expires today.</p>");
0116         }
0117         return msg.subs(keyInfo).toString();
0118     }
0119     KLocalizedString msg;
0120     if (isSigningKey) {
0121         msg = ki18np("<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expires tomorrow.</p>",
0122                      "<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expires in %1 days.</p>");
0123     } else if (isOwnKey) {
0124         msg = ki18np("<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expires tomorrow.</p>",
0125                      "<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expires in %1 days.</p>");
0126     } else {
0127         msg = ki18np("<p>The OpenPGP key for</p><p align=center>%2</p><p>expires tomorrow.</p>",
0128                      "<p>The OpenPGP key for</p><p align=center>%2</p><p>expires in %1 days.</p>");
0129     }
0130     return msg.subs(expiration.duration.count()).subs(keyInfo).toString();
0131 }
0132 
0133 QString formatSMIMEMessage(const GpgME::Key &orig_key, ExpiryChecker::Expiration expiration, ExpiryChecker::CheckFlags flags, bool ca)
0134 {
0135     const GpgME::Key key = expiration.certificate;
0136     const bool isOwnKey = flags & ExpiryChecker::OwnKey;
0137     const bool isSigningKey = flags & ExpiryChecker::SigningKey;
0138     const auto userCert = orig_key.isNull() ? key : orig_key;
0139     const auto userCertInfo = ki18nc("<b>User ID of certificate</b> (serial number serial no. of certificate)", "<b>%1</b> (serial number %2)")
0140                                   .subs(Kleo::DN(userCert.userID(0).id()).prettyDN())
0141                                   .subs(QString::fromLatin1(userCert.issuerSerial()));
0142     if (expiration.status == ExpiryChecker::Expired) {
0143         qCDebug(LIBKLEO_LOG) << "Certificate" << key << "expired" << expiration.duration.count() << "days ago";
0144         if (ca) {
0145             if (key.isRoot()) {
0146                 if (expiration.duration.count() == 0) {
0147                     KLocalizedString msg;
0148                     if (isSigningKey) {
0149                         msg = ki18n(
0150                             "<p>The root certificate</p><p align=center><b>%2</b></p>"
0151                             "<p>for your S/MIME signing certificate</p><p align=center>%1</p>"
0152                             "<p>expired less than a day ago.</p>");
0153                     } else if (isOwnKey) {
0154                         msg = ki18n(
0155                             "<p>The root certificate</p><p align=center><b>%2</b></p>"
0156                             "<p>for your S/MIME encryption certificate</p><p align=center>%1</p>"
0157                             "<p>expired less than a day ago.</p>");
0158                     } else {
0159                         msg = ki18n(
0160                             "<p>The root certificate</p><p align=center><b>%2</b></p>"
0161                             "<p>for S/MIME certificate</p><p align=center>%1</p>"
0162                             "<p>expired less than a day ago.</p>");
0163                     }
0164                     return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
0165                 }
0166                 KLocalizedString msg;
0167                 if (isSigningKey) {
0168                     msg = ki18np(
0169                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
0170                         "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
0171                         "<p>expired yesterday.</p>",
0172                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
0173                         "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
0174                         "<p>expired %1 days ago.</p>");
0175                 } else if (isOwnKey) {
0176                     msg = ki18np(
0177                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
0178                         "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
0179                         "<p>expired yesterday.</p>",
0180                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
0181                         "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
0182                         "<p>expired %1 days ago.</p>");
0183                 } else {
0184                     msg = ki18np(
0185                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
0186                         "<p>for S/MIME certificate</p><p align=center>%2</p>"
0187                         "<p>expired yesterday.</p>",
0188                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
0189                         "<p>for S/MIME certificate</p><p align=center>%2</p>"
0190                         "<p>expired %1 days ago.</p>");
0191                 }
0192                 return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
0193             } else {
0194                 if (expiration.duration.count() == 0) {
0195                     KLocalizedString msg;
0196                     if (isSigningKey) {
0197                         msg = ki18n(
0198                             "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
0199                             "<p>for your S/MIME signing certificate</p><p align=center>%1</p>"
0200                             "<p>expired less than a day ago.</p>");
0201                     } else if (isOwnKey) {
0202                         msg = ki18n(
0203                             "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
0204                             "<p>for your S/MIME encryption certificate</p><p align=center>%1</p>"
0205                             "<p>expired less than a day ago.</p>");
0206                     } else {
0207                         msg = ki18n(
0208                             "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
0209                             "<p>for S/MIME certificate</p><p align=center>%1</p>"
0210                             "<p>expired less than a day ago.</p>");
0211                     }
0212                     return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
0213                 }
0214                 KLocalizedString msg;
0215                 if (isSigningKey) {
0216                     msg = ki18np(
0217                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0218                         "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
0219                         "<p>expired yesterday.</p>",
0220                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0221                         "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
0222                         "<p>expired %1 days ago.</p>");
0223                 } else if (isOwnKey) {
0224                     msg = ki18np(
0225                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0226                         "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
0227                         "<p>expired yesterday.</p>",
0228                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0229                         "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
0230                         "<p>expired %1 days ago.</p>");
0231                 } else {
0232                     msg = ki18np(
0233                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0234                         "<p>for S/MIME certificate</p><p align=center>%2</p>"
0235                         "<p>expired yesterday.</p>",
0236                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0237                         "<p>for S/MIME certificate</p><p align=center>%2</p>"
0238                         "<p>expired %1 days ago.</p>");
0239                 }
0240                 return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
0241             }
0242         } else {
0243             if (expiration.duration.count() == 0) {
0244                 KLocalizedString msg;
0245                 if (isSigningKey) {
0246                     msg = ki18n("<p>Your S/MIME signing certificate</p><p align=center>%1</p><p>expired less than a day ago.</p>");
0247                 } else if (isOwnKey) {
0248                     msg = ki18n("<p>Your S/MIME encryption certificate</p><p align=center>%1</p><p>expired less than a day ago.</p>");
0249                 } else {
0250                     msg = ki18n("<p>The S/MIME certificate for</p><p align=center>%1</p><p>expired less than a day ago.</p>");
0251                 }
0252                 return msg.subs(userCertInfo).toString();
0253             }
0254             KLocalizedString msg;
0255             if (isSigningKey) {
0256                 msg = ki18np("<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expired yesterday.</p>",
0257                              "<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expired %1 days ago.</p>");
0258             } else if (isOwnKey) {
0259                 msg = ki18np("<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expired yesterday.</p>",
0260                              "<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expired %1 days ago.</p>");
0261             } else {
0262                 msg = ki18np("<p>The S/MIME certificate for</p><p align=center>%2</p><p>expired yesterday.</p>",
0263                              "<p>The S/MIME certificate for</p><p align=center>%2</p><p>expired %1 days ago.</p>");
0264             }
0265             return msg.subs(expiration.duration.count()).subs(userCertInfo).toString();
0266         }
0267     }
0268     qCDebug(LIBKLEO_LOG) << "Certificate" << key << "expires in" << expiration.duration.count() << "days";
0269     if (ca) {
0270         if (key.isRoot()) {
0271             if (expiration.duration.count() == 0) {
0272                 KLocalizedString msg;
0273                 if (isSigningKey) {
0274                     msg = ki18n(
0275                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
0276                         "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
0277                         "<p>expires today.</p>");
0278                 } else if (isOwnKey) {
0279                     msg = ki18n(
0280                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
0281                         "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
0282                         "<p>expires today.</p>");
0283                 } else {
0284                     msg = ki18n(
0285                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
0286                         "<p>for S/MIME certificate</p><p align=center>%2</p>"
0287                         "<p>expires today.</p>");
0288                 }
0289                 return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
0290             }
0291             KLocalizedString msg;
0292             if (isSigningKey) {
0293                 msg = ki18np(
0294                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
0295                     "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
0296                     "<p>expires tomorrow.</p>",
0297                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
0298                     "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
0299                     "<p>expires in %1 days.</p>");
0300             } else if (isOwnKey) {
0301                 msg = ki18np(
0302                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
0303                     "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
0304                     "<p>expires tomorrow.</p>",
0305                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
0306                     "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
0307                     "<p>expires in %1 days.</p>");
0308             } else {
0309                 msg = ki18np(
0310                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
0311                     "<p>for S/MIME certificate</p><p align=center>%2</p>"
0312                     "<p>expires tomorrow.</p>",
0313                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
0314                     "<p>for S/MIME certificate</p><p align=center>%2</p>"
0315                     "<p>expires in %1 days.</p>");
0316             }
0317             return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
0318         }
0319         if (expiration.duration.count() == 0) {
0320             KLocalizedString msg;
0321             if (isSigningKey) {
0322                 msg = ki18n(
0323                     "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0324                     "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
0325                     "<p>expires today.</p>");
0326             } else if (isOwnKey) {
0327                 msg = ki18n(
0328                     "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0329                     "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
0330                     "<p>expires today.</p>");
0331             } else {
0332                 msg = ki18n(
0333                     "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0334                     "<p>for S/MIME certificate</p><p align=center>%2</p>"
0335                     "<p>expires today.</p>");
0336             }
0337         }
0338         KLocalizedString msg;
0339         if (isSigningKey) {
0340             msg = ki18np(
0341                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0342                 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
0343                 "<p>expires tomorrow.</p>",
0344                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0345                 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
0346                 "<p>expires in %1 days.</p>");
0347         } else if (isOwnKey) {
0348             msg = ki18np(
0349                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0350                 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
0351                 "<p>expires tomorrow.</p>",
0352                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0353                 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
0354                 "<p>expires in %1 days.</p>");
0355         } else {
0356             msg = ki18np(
0357                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0358                 "<p>for S/MIME certificate</p><p align=center>%2</p>"
0359                 "<p>expires tomorrow.</p>",
0360                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
0361                 "<p>for S/MIME certificate</p><p align=center>%2</p>"
0362                 "<p>expires in %1 days.</p>");
0363         }
0364         return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
0365     }
0366     if (expiration.duration.count() == 0) {
0367         KLocalizedString msg;
0368         if (isSigningKey) {
0369             msg = ki18n("<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expires today.</p>");
0370         } else if (isOwnKey) {
0371             msg = ki18n("<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expires today.</p>");
0372         } else {
0373             msg = ki18n("<p>The S/MIME certificate for</p><p align=center>%2</p><p>expires today.</p>");
0374         }
0375         return msg.subs(userCertInfo).toString();
0376     }
0377     KLocalizedString msg;
0378     if (isSigningKey) {
0379         msg = ki18np(
0380             "<p>Your S/MIME signing certificate</p><p align=center>%2</p>"
0381             "<p>expires tomorrow.</p>",
0382             "<p>Your S/MIME signing certificate</p><p align=center>%2</p>"
0383             "<p>expires in %1 days.</p>");
0384     } else if (isOwnKey) {
0385         msg = ki18np(
0386             "<p>Your S/MIME encryption certificate</p><p align=center>%2</p>"
0387             "<p>expires tomorrow.</p>",
0388             "<p>Your S/MIME encryption certificate</p><p align=center>%2</p>"
0389             "<p>expires in %1 days.</p>");
0390     } else {
0391         msg = ki18np(
0392             "<p>The S/MIME certificate for</p><p align=center>%2</p>"
0393             "<p>expires tomorrow.</p>",
0394             "<p>The S/MIME certificate for</p><p align=center>%2</p>"
0395             "<p>expires in %1 days.</p>");
0396     }
0397     return msg.subs(expiration.duration.count()).subs(userCertInfo).toString();
0398 }
0399 
0400 static GpgME::Subkey findBestSubkey(const GpgME::Key &key, ExpiryChecker::CheckFlags usageFlags)
0401 {
0402     // find the subkey with the latest expiration date for the given usage flags
0403     if (!(usageFlags & ExpiryChecker::UsageMask)) {
0404         // return primary key if no specific usage is specified (as for chain certificates)
0405         return key.subkey(0);
0406     }
0407     GpgME::Subkey result;
0408     for (unsigned int i = 0; i < key.numSubkeys(); ++i) {
0409         const auto subkey = key.subkey(i);
0410         if (subkey.isRevoked() || subkey.isInvalid() || subkey.isDisabled()) {
0411             // unusable subkey
0412             continue;
0413         }
0414         if (((usageFlags & ExpiryChecker::EncryptionKey) && !subkey.canEncrypt()) //
0415             || ((usageFlags & ExpiryChecker::SigningKey) && !subkey.canSign()) //
0416             || ((usageFlags & ExpiryChecker::CertificationKey) && !subkey.canCertify())) {
0417             // unsuitable subkey for requested usage
0418             continue;
0419         }
0420         if (subkey.neverExpires()) {
0421             // stop looking for the best subkey if we found a suitable subkey that doesn't expire;
0422             // return the primary key because a non-expiring subkey inherits the primary key's expiration
0423             return key.subkey(0);
0424         }
0425         if (quint32(subkey.expirationTime()) > quint32(result.expirationTime())) {
0426             result = subkey;
0427         }
0428     }
0429     return result;
0430 }
0431 
0432 ExpiryChecker::Expiration ExpiryCheckerPrivate::calculateExpiration(const GpgME::Subkey &subkey) const
0433 {
0434     if (subkey.neverExpires()) {
0435         return {subkey.parent(), ExpiryChecker::NotNearExpiry, Kleo::chrono::days::zero()};
0436     }
0437     const time_t currentTime = timeProvider ? timeProvider->currentTime() : std::time(nullptr);
0438     const auto currentDate = timeProvider ? timeProvider->currentDate() : QDate::currentDate();
0439     const auto timeSpec = timeProvider ? timeProvider->timeSpec() : Qt::LocalTime;
0440     const time_t expirationTime = subkey.expirationTime();
0441     const auto expirationDate = QDateTime::fromSecsSinceEpoch(quint32(expirationTime), timeSpec).date();
0442     // use std::difftime to avoid problems with negative values on 32-bit systems
0443     if (std::difftime(expirationTime, currentTime) <= 0) {
0444         return {subkey.parent(), ExpiryChecker::Expired, Kleo::chrono::days{expirationDate.daysTo(currentDate)}};
0445     } else {
0446         return {subkey.parent(), ExpiryChecker::ExpiresSoon, Kleo::chrono::days{currentDate.daysTo(expirationDate)}};
0447     }
0448 }
0449 
0450 ExpiryChecker::Expiration ExpiryCheckerPrivate::checkForExpiration(const GpgME::Key &key, //
0451                                                                    Kleo::chrono::days threshold,
0452                                                                    ExpiryChecker::CheckFlags usageFlags) const
0453 {
0454     const auto subkey = findBestSubkey(key, usageFlags);
0455     if (subkey.isNull()) {
0456         return {key, ExpiryChecker::NoSuitableSubkey, {}};
0457     }
0458     ExpiryChecker::Expiration expiration = calculateExpiration(subkey);
0459     if ((expiration.status == ExpiryChecker::ExpiresSoon) && (expiration.duration > threshold)) {
0460         // key expires, but not too soon
0461         expiration.status = ExpiryChecker::NotNearExpiry;
0462     }
0463     return expiration;
0464 }
0465 
0466 ExpiryChecker::Result ExpiryCheckerPrivate::checkKeyNearExpiry(const GpgME::Key &orig_key, ExpiryChecker::CheckFlags flags)
0467 {
0468     static const int maximumCertificateChainLength = 100;
0469     const bool isOwnKey = flags & ExpiryChecker::OwnKey;
0470 
0471     ExpiryChecker::Result result;
0472     result.checkFlags = flags;
0473     result.expiration.certificate = orig_key;
0474 
0475     // use vector instead of set because certificate chains are usually very short
0476     std::vector<std::string> checkedCertificates;
0477     auto key = orig_key;
0478     for (int chainCount = 0; chainCount < maximumCertificateChainLength; ++chainCount) {
0479         checkedCertificates.push_back(key.primaryFingerprint());
0480 
0481         const GpgME::Subkey subkey = key.subkey(0);
0482 
0483         const bool newMessage = !alreadyWarnedFingerprints.count(subkey.fingerprint());
0484 
0485         const auto threshold = chainCount > 0 //
0486             ? (key.isRoot() ? settings.rootCertThreshold() : settings.chainCertThreshold()) //
0487             : (isOwnKey ? settings.ownKeyThreshold() : settings.otherKeyThreshold());
0488         const auto usageFlags = (chainCount == 0) ? (flags & ExpiryChecker::UsageMask) : ExpiryChecker::CheckFlags{};
0489         const auto expiration = checkForExpiration(key, threshold, usageFlags);
0490         if (chainCount == 0) {
0491             result.expiration = expiration;
0492         } else if (expiration.status != ExpiryChecker::NotNearExpiry) {
0493             result.chainExpiration.push_back(expiration);
0494         }
0495         if (expiration.status == ExpiryChecker::Expired) {
0496             const QString msg = key.protocol() == GpgME::OpenPGP //
0497                 ? formatOpenPGPMessage(expiration, flags)
0498                 : formatSMIMEMessage(orig_key, expiration, flags, chainCount > 0);
0499             alreadyWarnedFingerprints.insert(subkey.fingerprint());
0500             Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyExpired : ExpiryChecker::OtherKeyExpired, newMessage);
0501         } else if (expiration.status == ExpiryChecker::ExpiresSoon) {
0502             const QString msg = key.protocol() == GpgME::OpenPGP //
0503                 ? formatOpenPGPMessage(expiration, flags)
0504                 : formatSMIMEMessage(orig_key, expiration, flags, chainCount > 0);
0505             alreadyWarnedFingerprints.insert(subkey.fingerprint());
0506             Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyNearExpiry : ExpiryChecker::OtherKeyNearExpiry, newMessage);
0507         } else if (expiration.status == ExpiryChecker::NoSuitableSubkey) {
0508             break;
0509         }
0510         if (!(flags & ExpiryChecker::CheckChain) || key.isRoot() || (key.protocol() != GpgME::CMS)) {
0511             break;
0512         }
0513         const auto keys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption);
0514         if (keys.empty()) {
0515             break;
0516         }
0517         key = keys.front();
0518         if (Kleo::contains(checkedCertificates, key.primaryFingerprint())) {
0519             break; // this certificate was already checked (looks like a circle in the chain)
0520         }
0521     }
0522     return result;
0523 }
0524 
0525 ExpiryChecker::Result ExpiryChecker::checkKey(const GpgME::Key &key, CheckFlags flags) const
0526 {
0527     if (key.isNull()) {
0528         qWarning(LIBKLEO_LOG) << __func__ << "called with null key";
0529         return {flags, {key, InvalidKey, {}}, {}};
0530     }
0531     if (!(flags & UsageMask)) {
0532         qWarning(LIBKLEO_LOG) << __func__ << "called with invalid flags:" << flags;
0533         return {flags, {key, InvalidCheckFlags, {}}, {}};
0534     }
0535     return d->checkKeyNearExpiry(key, flags);
0536 }
0537 
0538 void ExpiryChecker::setTimeProviderForTest(const std::shared_ptr<TimeProvider> &timeProvider)
0539 {
0540     d->timeProvider = timeProvider;
0541 }
0542 
0543 #include "moc_expirychecker.cpp"