diff --git a/autotests/expirycheckertest.cpp b/autotests/expirycheckertest.cpp index a6e682365..9493d39bb 100644 --- a/autotests/expirycheckertest.cpp +++ b/autotests/expirycheckertest.cpp @@ -1,299 +1,299 @@ /* This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2022 Sandro Knauß SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; using days = Kleo::chrono::days; class FakeTimeProvider : public Kleo::TimeProvider { public: explicit FakeTimeProvider(const QDate &date) : mTime{date.startOfDay(Qt::UTC).toSecsSinceEpoch()} { } time_t getTime() const override { return mTime; } private: time_t mTime; }; class ExpiryCheckerTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { qRegisterMetaType(); mGnupgHome = QTest::qExtractTestData(QStringLiteral("/fixtures/expirycheckertest")); qputenv("GNUPGHOME", mGnupgHome->path().toLocal8Bit()); // hold a reference to the key cache to avoid rebuilding while the test is running mKeyCache = KeyCache::instance(); // make sure that the key cache has been populated (void)mKeyCache->keys(); } void cleanupTestCase() { // verify that nobody else holds a reference to the key cache QVERIFY(mKeyCache.use_count() == 1); mKeyCache.reset(); (void)QProcess::execute(QStringLiteral("gpgconf"), {"--kill", "all"}); mGnupgHome.reset(); qunsetenv("GNUPGHOME"); } void valid_data() { QTest::addColumn("key"); QTest::addColumn("fakedate"); // use dates between creation date and expiration date (if there is one) of the test keys/certificates QTest::newRow("neverExpire") << testKey("test@kolab.org", GpgME::OpenPGP) << QDate{2012, 1, 1}; QTest::newRow("openpgp") << testKey("alice@autocrypt.example", GpgME::OpenPGP) << QDate{2020, 1, 1}; QTest::newRow("smime") << testKey("test@example.com", GpgME::CMS) << QDate{2012, 1, 1}; } void valid() { QFETCH(GpgME::Key, key); QFETCH(QDate, fakedate); ExpiryChecker checker(ExpiryCheckerSettings{days{1}, days{1}, days{1}, days{1}}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); - checker.checkKey(key); + checker.checkKey(key, ExpiryChecker::OtherKey); QCOMPARE(spy.count(), 0); } void expired_data() { QTest::addColumn("key"); QTest::addColumn("fakedate"); QTest::addColumn("msg"); QTest::addColumn("msgOwnKey"); QTest::addColumn("msgOwnSigningKey"); // use the day after the expiration date of the test keys/certificates as fake date QTest::newRow("openpgp") << testKey("alice@autocrypt.example", GpgME::OpenPGP) // << QDate{2021, 1, 22} << QStringLiteral( "

The OpenPGP key for

alice@autocrypt.example (KeyID 0xF231550C4F47E38E)

expired less than a day ago.

") << QStringLiteral( "

Your OpenPGP encryption key

alice@autocrypt.example (KeyID 0xF231550C4F47E38E)

expired less than a day " "ago.

") << QStringLiteral( "

Your OpenPGP signing key

alice@autocrypt.example (KeyID 0xF231550C4F47E38E)

expired less than a day " "ago.

"); QTest::newRow("smime") << testKey("test@example.com", GpgME::CMS) // << QDate{2013, 3, 26} << QStringLiteral( "

The S/MIME certificate for

CN=unittest cert,EMAIL=test@example.com,O=KDAB,C=US (serial " "number 00D345203A186385C9)

expired less than a day ago.

") << QStringLiteral( "

Your S/MIME encryption certificate

CN=unittest cert,EMAIL=test@example.com,O=KDAB,C=US " "(serial number 00D345203A186385C9)

expired less than a day ago.

") << QStringLiteral( "

Your S/MIME signing certificate

CN=unittest cert,EMAIL=test@example.com,O=KDAB,C=US " "(serial number 00D345203A186385C9)

expired less than a day ago.

"); } void expired() { QFETCH(GpgME::Key, key); QFETCH(QDate, fakedate); QFETCH(QString, msg); QFETCH(QString, msgOwnKey); QFETCH(QString, msgOwnSigningKey); { ExpiryChecker checker(ExpiryCheckerSettings{days{1}, days{1}, days{1}, days{1}}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); - checker.checkKey(key); + checker.checkKey(key, ExpiryChecker::OtherKey); QCOMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(0).value().keyID(), key.keyID()); QCOMPARE(arguments.at(1).toString(), msg); QCOMPARE(arguments.at(2).value(), ExpiryChecker::OtherKeyExpired); } { ExpiryChecker checker(ExpiryCheckerSettings{days{1}, days{1}, days{1}, days{1}}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); - checker.checkOwnKey(key); + checker.checkKey(key, ExpiryChecker::OwnKey); QCOMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(0).value().keyID(), key.keyID()); QCOMPARE(arguments.at(1).toString(), msgOwnKey); QCOMPARE(arguments.at(2).value(), ExpiryChecker::OwnKeyExpired); } { ExpiryChecker checker(ExpiryCheckerSettings{days{1}, days{1}, days{1}, days{1}}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); - checker.checkOwnSigningKey(key); + checker.checkKey(key, ExpiryChecker::OwnSigningKey); QCOMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(0).value().keyID(), key.keyID()); QCOMPARE(arguments.at(1).toString(), msgOwnSigningKey); QCOMPARE(arguments.at(2).value(), ExpiryChecker::OwnKeyExpired); } } void nearexpiry_data() { QTest::addColumn("key"); QTest::addColumn("fakedate"); QTest::addColumn("msg"); QTest::addColumn("msgOwnKey"); QTest::addColumn("msgOwnSigningKey"); // use the day 5 days before the expiration date of the test keys/certificates as fake date QTest::newRow("openpgp") << testKey("alice@autocrypt.example", GpgME::OpenPGP) // << QDate{2021, 1, 16} << QStringLiteral( "

The OpenPGP key for

alice@autocrypt.example (KeyID 0xF231550C4F47E38E)

expires in less than 6 days.

") << QStringLiteral( "

Your OpenPGP encryption key

alice@autocrypt.example (KeyID 0xF231550C4F47E38E)

expires in less than 6 " "days.

") << QStringLiteral( "

Your OpenPGP signing key

alice@autocrypt.example (KeyID 0xF231550C4F47E38E)

expires in less than 6 " "days.

"); QTest::newRow("smime") << testKey("test@example.com", GpgME::CMS) // << QDate{2013, 3, 20} << QStringLiteral( "

The S/MIME certificate for

CN=unittest cert,EMAIL=test@example.com,O=KDAB,C=US (serial " "number 00D345203A186385C9);

expires in less than 6 days.

") << QStringLiteral( "

Your S/MIME encryption certificate

CN=unittest cert,EMAIL=test@example.com,O=KDAB,C=US " "(serial number 00D345203A186385C9);

expires in less than 6 days.

") << QStringLiteral( "

Your S/MIME signing certificate

CN=unittest cert,EMAIL=test@example.com,O=KDAB,C=US " "(serial number 00D345203A186385C9);

expires in less than 6 days.

"); } void nearexpiry() { QFETCH(GpgME::Key, key); QFETCH(QDate, fakedate); QFETCH(QString, msg); QFETCH(QString, msgOwnKey); QFETCH(QString, msgOwnSigningKey); { ExpiryChecker checker(ExpiryCheckerSettings{days{1}, days{10}, days{1}, days{1}}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); // Test if the correct treshold is taken - checker.checkKey(key); - checker.checkOwnKey(key); - checker.checkOwnSigningKey(key); + checker.checkKey(key, ExpiryChecker::OtherKey); + checker.checkKey(key, ExpiryChecker::OwnKey); + checker.checkKey(key, ExpiryChecker::OwnSigningKey); QCOMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(0).value().keyID(), key.keyID()); QCOMPARE(arguments.at(1).toString(), msg); QCOMPARE(arguments.at(2).value(), ExpiryChecker::OtherKeyNearExpiry); } { ExpiryChecker checker(ExpiryCheckerSettings{days{10}, days{1}, days{1}, days{1}}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); // Test if the correct treshold is taken - checker.checkKey(key); - checker.checkOwnKey(key); + checker.checkKey(key, ExpiryChecker::OtherKey); + checker.checkKey(key, ExpiryChecker::OwnKey); QCOMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(0).value().keyID(), key.keyID()); QCOMPARE(arguments.at(1).toString(), msgOwnKey); QCOMPARE(arguments.at(2).value(), ExpiryChecker::OwnKeyNearExpiry); } { ExpiryChecker checker(ExpiryCheckerSettings{days{10}, days{1}, days{1}, days{1}}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); // Test if the correct treshold is taken - checker.checkKey(key); - checker.checkOwnSigningKey(key); + checker.checkKey(key, ExpiryChecker::OtherKey); + checker.checkKey(key, ExpiryChecker::OwnSigningKey); QCOMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(0).value().keyID(), key.keyID()); QCOMPARE(arguments.at(1).toString(), msgOwnSigningKey); QCOMPARE(arguments.at(2).value(), ExpiryChecker::OwnKeyNearExpiry); } } private: // OpenPGP keys // // pub rsa2048 2009-11-13 [SC] // 1BA323932B3FAA826132C79E8D9860C58F246DE6 // uid [ultimate] unittest key (no password) // sub rsa2048 2009-11-13 [E] // // pub ed25519 2019-01-22 [SC] [expired: 2021-01-21] // EB85BB5FA33A75E15E944E63F231550C4F47E38E // uid [ expired] alice@autocrypt.example // // S/MIME certificates // // ID: 0x212B49DC // S/N: 00D345203A186385C9 // (dec): 15223609549285197257 // Issuer: /CN=unittest cert/O=KDAB/C=US/EMail=test@example.com // Subject: /CN=unittest cert/O=KDAB/C=US/EMail=test@example.com // validity: 2010-06-29 13:48:23 through 2013-03-25 13:48:23 // key type: rsa1024 // chain length: unlimited // sha1 fpr: 24:D2:FC:A2:2E:B3:B8:0A:1E:37:71:D1:4C:C6:58:E3:21:2B:49:DC // sha2 fpr: 62:4B:A4:B8:7D:8F:99:AA:6B:46:E3:C8:C5:BE:BF:30:29:B6:EC:4E:CC:7D:1F:9F:A8:39:B6:CE:03:6F:C7:FB Key testKey(const char *email, Protocol protocol = UnknownProtocol) { const std::vector keys = KeyCache::instance()->findByEMailAddress(email); for (const auto &key : keys) { if (protocol == UnknownProtocol || key.protocol() == protocol) { return key; } } qWarning() << "No" << Formatting::displayName(protocol) << "test key found for" << email; return {}; } private: QSharedPointer mGnupgHome; std::shared_ptr mKeyCache; }; QTEST_MAIN(ExpiryCheckerTest) #include "expirycheckertest.moc" diff --git a/src/kleo/expirychecker.cpp b/src/kleo/expirychecker.cpp index 2d67bf3be..d939cb270 100644 --- a/src/kleo/expirychecker.cpp +++ b/src/kleo/expirychecker.cpp @@ -1,453 +1,449 @@ /* This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021 Sandro Knauß SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker Based on kpgp.h Copyright (C) 2001,2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: LGPL-2.0-or-later */ #include "expirychecker.h" #include "debug.h" #include "dn.h" #include "expirycheckersettings.h" #include #include #include #include #include #include #include #include using namespace Kleo; namespace { struct Expiration { enum Status { NeverExpires, Expires, Expired, } status; // duration is full days until expiry if status is Expires, // full days since expiry if status is Expired, // undefined if status is NeverExpires Kleo::chrono::days duration; }; } class Kleo::ExpiryCheckerPrivate { Kleo::ExpiryChecker *q; public: ExpiryCheckerPrivate(ExpiryChecker *qq, const ExpiryCheckerSettings &settings_) : q{qq} , settings{settings_} { } Expiration calculateExpiration(const GpgME::Subkey &key) const; void checkKeyNearExpiry(const GpgME::Key &key, - bool isOwnKey, - bool isSigningKey, + ExpiryChecker::KeyFlags flags, bool ca = false, int recur_limit = 100, const GpgME::Key &orig_key = GpgME::Key::null); ExpiryCheckerSettings settings; std::set alreadyWarnedFingerprints; std::shared_ptr timeProvider; }; ExpiryChecker::ExpiryChecker(const ExpiryCheckerSettings &settings) : d{new ExpiryCheckerPrivate{this, settings}} { } ExpiryChecker::~ExpiryChecker() = default; ExpiryCheckerSettings ExpiryChecker::settings() const { return d->settings; } -QString formatOpenPGPMessage(const GpgME::Key &key, Expiration expiration, bool isOwnKey, bool isSigningKey) +QString formatOpenPGPMessage(const GpgME::Key &key, Expiration expiration, ExpiryChecker::KeyFlags flags) { + const bool isOwnKey = flags & ExpiryChecker::OwnKey; + const bool isSigningKey = flags & ExpiryChecker::SigningKey; const auto keyInfo = ki18nc("User ID of key (KeyID key ID of key in hex notation)", "%1 (KeyID 0x%2)") .subs(QString::fromUtf8(key.userID(0).id())) .subs(QString::fromLatin1(key.keyID())); if (expiration.status == Expiration::Expired) { qCDebug(LIBKLEO_LOG) << "Key" << key << "expired" << expiration.duration.count() << "days ago"; if (expiration.duration.count() == 0) { KLocalizedString msg; if (isSigningKey) { msg = ki18n("

Your OpenPGP signing key

%1

expired less than a day ago.

"); } else if (isOwnKey) { msg = ki18n("

Your OpenPGP encryption key

%1

expired less than a day ago.

"); } else { msg = ki18n("

The OpenPGP key for

%1

expired less than a day ago.

"); } return msg.subs(keyInfo).toString(); } KLocalizedString msg; if (isSigningKey) { msg = ki18np( "

Your OpenPGP signing key

%2

" "

expired one day ago.

", "

Your OpenPGP signing key

%2

" "

expired %1 days ago.

"); } else if (isOwnKey) { msg = ki18np( "

Your OpenPGP encryption key

%2

" "

expired one day ago.

", "

Your OpenPGP encryption key

%2

" "

expired %1 days ago.

"); } else { msg = ki18np( "

The OpenPGP key for

%2

" "

expired one day ago.

", "

The OpenPGP key for

%2

" "

expired %1 days ago.

"); } return msg.subs(expiration.duration.count()).subs(keyInfo).toString(); } qCDebug(LIBKLEO_LOG) << "Key" << key << "expires in less than" << expiration.duration.count() + 1 << "days"; KLocalizedString msg; if (isSigningKey) { msg = ki18np( "

Your OpenPGP signing key

%2

" "

expires in less than a day.

", "

Your OpenPGP signing key

%2

" "

expires in less than %1 days.

"); } else if (isOwnKey) { msg = ki18np( "

Your OpenPGP encryption key

%2

" "

expires in less than a day.

", "

Your OpenPGP encryption key

%2

" "

expires in less than %1 days.

"); } else { msg = ki18np( "

The OpenPGP key for

%2

" "

expires in less than a day.

", "

The OpenPGP key for

%2

" "

expires in less than %1 days.

"); } return msg.subs(expiration.duration.count() + 1).subs(keyInfo).toString(); } -QString formatSMIMEMessage(const GpgME::Key &key, const GpgME::Key &orig_key, Expiration expiration, bool isOwnKey, bool isSigningKey, bool ca) +QString formatSMIMEMessage(const GpgME::Key &key, const GpgME::Key &orig_key, Expiration expiration, ExpiryChecker::KeyFlags flags, bool ca) { + const bool isOwnKey = flags & ExpiryChecker::OwnKey; + const bool isSigningKey = flags & ExpiryChecker::SigningKey; const auto userCert = orig_key.isNull() ? key : orig_key; const auto userCertInfo = ki18nc("User ID of certificate (serial number serial no. of certificate)", "%1 (serial number %2)") .subs(Kleo::DN(userCert.userID(0).id()).prettyDN()) .subs(QString::fromLatin1(userCert.issuerSerial())); if (expiration.status == Expiration::Expired) { qCDebug(LIBKLEO_LOG) << "Certificate" << key << "expired" << expiration.duration.count() << "days ago"; if (ca) { if (key.isRoot()) { if (expiration.duration.count() == 0) { KLocalizedString msg; if (isSigningKey) { msg = ki18n( "

The root certificate

%2

" "

for your S/MIME signing certificate

%1

" "

expired less than a day ago.

"); } else if (isOwnKey) { msg = ki18n( "

The root certificate

%2

" "

for your S/MIME encryption certificate

%1

" "

expired less than a day ago.

"); } else { msg = ki18n( "

The root certificate

%2

" "

for S/MIME certificate

%1

" "

expired less than a day ago.

"); } return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString(); } KLocalizedString msg; if (isSigningKey) { msg = ki18np( "

The root certificate

%3

" "

for your S/MIME signing certificate

%2

" "

expired one day ago.

", "

The root certificate

%3

" "

for your S/MIME signing certificate

%2

" "

expired %1 days ago.

"); } else if (isOwnKey) { msg = ki18np( "

The root certificate

%3

" "

for your S/MIME encryption certificate

%2

" "

expired one day ago.

", "

The root certificate

%3

" "

for your S/MIME encryption certificate

%2

" "

expired %1 days ago.

"); } else { msg = ki18np( "

The root certificate

%3

" "

for S/MIME certificate

%2

" "

expired one day ago.

", "

The root certificate

%3

" "

for S/MIME certificate

%2

" "

expired %1 days ago.

"); } return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString(); } else { if (expiration.duration.count() == 0) { KLocalizedString msg; if (isSigningKey) { msg = ki18n( "

The intermediate CA certificate

%2

" "

for your S/MIME signing certificate

%1

" "

expired less than a day ago.

"); } else if (isOwnKey) { msg = ki18n( "

The intermediate CA certificate

%2

" "

for your S/MIME encryption certificate

%1

" "

expired less than a day ago.

"); } else { msg = ki18n( "

The intermediate CA certificate

%2

" "

for S/MIME certificate

%1

" "

expired less than a day ago.

"); } return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString(); } KLocalizedString msg; if (isSigningKey) { msg = ki18np( "

The intermediate CA certificate

%3

" "

for your S/MIME signing certificate

%2

" "

expired less than a day ago.

", "

The intermediate CA certificate

%3

" "

for your S/MIME signing certificate

%2

" "

expired %1 days ago.

"); } else if (isOwnKey) { msg = ki18np( "

The intermediate CA certificate

%3

" "

for your S/MIME encryption certificate

%2

" "

expired less than a day ago.

", "

The intermediate CA certificate

%3

" "

for your S/MIME encryption certificate

%2

" "

expired %1 days ago.

"); } else { msg = ki18np( "

The intermediate CA certificate

%3

" "

for S/MIME certificate

%2

" "

expired less than a day ago.

", "

The intermediate CA certificate

%3

" "

for S/MIME certificate

%2

" "

expired %1 days ago.

"); } return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString(); } } else { if (expiration.duration.count() == 0) { KLocalizedString msg; if (isSigningKey) { msg = ki18n("

Your S/MIME signing certificate

%1

expired less than a day ago.

"); } else if (isOwnKey) { msg = ki18n("

Your S/MIME encryption certificate

%1

expired less than a day ago.

"); } else { msg = ki18n("

The S/MIME certificate for

%1

expired less than a day ago.

"); } return msg.subs(userCertInfo).toString(); } KLocalizedString msg; if (isSigningKey) { msg = ki18np( "

Your S/MIME signing certificate

%2

" "

expired less than a day ago.

", "

Your S/MIME signing certificate

%2

" "

expired %1 days ago.

"); } else if (isOwnKey) { msg = ki18np( "

Your S/MIME encryption certificate

%2

" "

expired less than a day ago.

", "

Your S/MIME encryption certificate

%2

" "

expired %1 days ago.

"); } else { msg = ki18np( "

The S/MIME certificate for

%2

" "

expired less than a day ago.

", "

The S/MIME certificate for

%2

" "

expired %1 days ago.

"); } return msg.subs(expiration.duration.count()).subs(userCertInfo).toString(); } } qCDebug(LIBKLEO_LOG) << "Certificate" << key << "expires in less than" << expiration.duration.count() + 1 << "days"; KLocalizedString msg; if (ca) { if (key.isRoot()) { if (isSigningKey) { msg = ki18np( "

The root certificate

%3

" "

for your S/MIME signing certificate

%2;

" "

expires in less than a day.

", "

The root certificate

%3

" "

for your S/MIME signing certificate

%2;

" "

expires in less than %1 days.

"); } else if (isOwnKey) { msg = ki18np( "

The root certificate

%3

" "

for your S/MIME encryption certificate

%2;

" "

expires in less than a day.

", "

The root certificate

%3

" "

for your S/MIME encryption certificate

%2;

" "

expires in less than %1 days.

"); } else { msg = ki18np( "

The root certificate

%3

" "

for S/MIME certificate

%2;

" "

expires in less than a day.

", "

The root certificate

%3

" "

for S/MIME certificate

%2;

" "

expires in less than %1 days.

"); } } else { if (isSigningKey) { msg = ki18np( "

The intermediate CA certificate

%3

" "

for your S/MIME signing certificate

%2;

" "

expires in less than a day.

", "

The intermediate CA certificate

%3

" "

for your S/MIME signing certificate

%2;

" "

expires in less than %1 days.

"); } else if (isOwnKey) { msg = ki18np( "

The intermediate CA certificate

%3

" "

for your S/MIME encryption certificate

%2;

" "

expires in less than a day.

", "

The intermediate CA certificate

%3

" "

for your S/MIME encryption certificate

%2;

" "

expires in less than %1 days.

"); } else { msg = ki18np( "

The intermediate CA certificate

%3

" "

for S/MIME certificate

%2;

" "

expires in less than a day.

", "

The intermediate CA certificate

%3

" "

for S/MIME certificate

%2;

" "

expires in less than %1 days.

"); } } return msg.subs(expiration.duration.count() + 1).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString(); } if (isSigningKey) { msg = ki18np( "

Your S/MIME signing certificate

%2;

" "

expires in less than a day.

", "

Your S/MIME signing certificate

%2;

" "

expires in less than %1 days.

"); } else if (isOwnKey) { msg = ki18np( "

Your S/MIME encryption certificate

%2;

" "

expires in less than a day.

", "

Your S/MIME encryption certificate

%2;

" "

expires in less than %1 days.

"); } else { msg = ki18np( "

The S/MIME certificate for

%2;

" "

expires in less than a day.

", "

The S/MIME certificate for

%2;

" "

expires in less than %1 days.

"); } return msg.subs(expiration.duration.count() + 1).subs(userCertInfo).toString(); } Expiration ExpiryCheckerPrivate::calculateExpiration(const GpgME::Subkey &subkey) const { if (subkey.neverExpires()) { return {Expiration::NeverExpires, Kleo::chrono::days::zero()}; } const time_t t = timeProvider ? timeProvider->getTime() : std::time(nullptr); // casting the double-valued difference (returned by std::difftime) of two non-negative time_t to a time_t is no problem; // negative values for expiration time and current time can be safely ignored const time_t secsTillExpiry = static_cast(std::difftime(subkey.expirationTime(), t)); return {secsTillExpiry <= 0 ? Expiration::Expired : Expiration::Expires, std::chrono::duration_cast(std::chrono::seconds{std::abs(secsTillExpiry)})}; } -void ExpiryCheckerPrivate::checkKeyNearExpiry(const GpgME::Key &key, bool isOwnKey, bool isSigningKey, bool ca, int recur_limit, const GpgME::Key &orig_key) +void ExpiryCheckerPrivate::checkKeyNearExpiry(const GpgME::Key &key, ExpiryChecker::KeyFlags flags, bool ca, int recur_limit, const GpgME::Key &orig_key) { + const bool isOwnKey = flags & ExpiryChecker::OwnKey; if (recur_limit <= 0) { qCDebug(LIBKLEO_LOG) << "Key chain too long (>100 certs)"; return; } const GpgME::Subkey subkey = key.subkey(0); const bool newMessage = !alreadyWarnedFingerprints.count(subkey.fingerprint()); const auto expiration = calculateExpiration(subkey); if (expiration.status == Expiration::NeverExpires) { return; } if (expiration.status == Expiration::Expired) { - const QString msg = key.protocol() == GpgME::OpenPGP ? formatOpenPGPMessage(key, expiration, isOwnKey, isSigningKey) - : formatSMIMEMessage(key, orig_key, expiration, isOwnKey, isSigningKey, ca); + const QString msg = key.protocol() == GpgME::OpenPGP // + ? formatOpenPGPMessage(key, expiration, flags) + : formatSMIMEMessage(key, orig_key, expiration, flags, ca); alreadyWarnedFingerprints.insert(subkey.fingerprint()); Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyExpired : ExpiryChecker::OtherKeyExpired, newMessage); } else { const auto threshold = ca // ? (key.isRoot() ? settings.rootCertThreshold() : settings.chainCertThreshold()) // : (isOwnKey ? settings.ownKeyThreshold() : settings.otherKeyThreshold()); if (threshold >= Kleo::chrono::days::zero() && expiration.duration <= threshold) { - const QString msg = key.protocol() == GpgME::OpenPGP ? formatOpenPGPMessage(key, expiration, isOwnKey, isSigningKey) - : formatSMIMEMessage(key, orig_key, expiration, isOwnKey, isSigningKey, ca); + const QString msg = key.protocol() == GpgME::OpenPGP // + ? formatOpenPGPMessage(key, expiration, flags) + : formatSMIMEMessage(key, orig_key, expiration, flags, ca); alreadyWarnedFingerprints.insert(subkey.fingerprint()); Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyNearExpiry : ExpiryChecker::OtherKeyNearExpiry, newMessage); } } if (key.isRoot()) { return; } else if (key.protocol() != GpgME::CMS) { // Key chaining is only possible in SMIME return; } else if (const char *chain_id = key.chainID()) { QGpgME::Protocol *p = QGpgME::smime(); Q_ASSERT(p); std::unique_ptr job(p->keyListJob(false, false, true)); if (job.get()) { std::vector keys; job->exec(QStringList(QLatin1String(chain_id)), false, keys); if (!keys.empty()) { - return checkKeyNearExpiry(keys.front(), isOwnKey, isSigningKey, true, recur_limit - 1, ca ? orig_key : key); + return checkKeyNearExpiry(keys.front(), flags, true, recur_limit - 1, ca ? orig_key : key); } } } } -void ExpiryChecker::checkOwnSigningKey(const GpgME::Key &key) const +void ExpiryChecker::checkKey(const GpgME::Key &key, KeyFlags flags) const { - d->checkKeyNearExpiry(key, /*isOwnKey*/ true, /*isSigningKey*/ true); -} - -void ExpiryChecker::checkOwnKey(const GpgME::Key &key) const -{ - d->checkKeyNearExpiry(key, /*isOwnKey*/ true, /*isSigningKey*/ false); -} - -void ExpiryChecker::checkKey(const GpgME::Key &key) const -{ - d->checkKeyNearExpiry(key, false, false); + d->checkKeyNearExpiry(key, flags); } void ExpiryChecker::setTimeProviderForTest(const std::shared_ptr &timeProvider) { d->timeProvider = timeProvider; } diff --git a/src/kleo/expirychecker.h b/src/kleo/expirychecker.h index 528147484..5b99f47c9 100644 --- a/src/kleo/expirychecker.h +++ b/src/kleo/expirychecker.h @@ -1,73 +1,82 @@ /* This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021 Sandro Knauß SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker Based on kpgp.h Copyright (C) 2001,2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include "kleo_export.h" #include #include #include #include namespace Kleo { class ExpiryCheckerPrivate; class ExpiryCheckerSettings; class KLEO_EXPORT TimeProvider { public: virtual ~TimeProvider() = default; virtual time_t getTime() const = 0; }; class KLEO_EXPORT ExpiryChecker : public QObject { Q_OBJECT public: + enum KeyFlag { + OtherKey = 0, + OwnKey = 1, + SigningKey = 2, + OwnEncryptionKey = OwnKey, + OwnSigningKey = OwnKey | SigningKey, + }; + Q_DECLARE_FLAGS(KeyFlags, KeyFlag) + explicit ExpiryChecker(const ExpiryCheckerSettings &settings); ~ExpiryChecker() override; Q_REQUIRED_RESULT ExpiryCheckerSettings settings() const; enum ExpiryInformation { OwnKeyExpired, OwnKeyNearExpiry, OtherKeyExpired, OtherKeyNearExpiry, }; Q_ENUM(ExpiryInformation) - void checkOwnSigningKey(const GpgME::Key &key) const; - void checkOwnKey(const GpgME::Key &key) const; - void checkKey(const GpgME::Key &key) const; + void checkKey(const GpgME::Key &key, KeyFlags flags) const; Q_SIGNALS: void expiryMessage(const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) const; public: void setTimeProviderForTest(const std::shared_ptr &); private: std::unique_ptr const d; }; + +Q_DECLARE_OPERATORS_FOR_FLAGS(ExpiryChecker::KeyFlags) } Q_DECLARE_METATYPE(GpgME::Key)