diff --git a/autotests/expirycheckertest.cpp b/autotests/expirycheckertest.cpp index f6388bffd..de247c46c 100644 --- a/autotests/expirycheckertest.cpp +++ b/autotests/expirycheckertest.cpp @@ -1,295 +1,298 @@ /* 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 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(1, 1, 1, 1); + ExpiryChecker checker(days{1}, days{1}, days{1}, days{1}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); checker.checkKey(key); 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(1, 1, 1, 1); + ExpiryChecker checker(days{1}, days{1}, days{1}, days{1}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); checker.checkKey(key); 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(1, 1, 1, 1); + ExpiryChecker checker(days{1}, days{1}, days{1}, days{1}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); checker.checkOwnKey(key); 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(1, 1, 1, 1); + ExpiryChecker checker(days{1}, days{1}, days{1}, days{1}); checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); checker.checkOwnSigningKey(key); 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(1, 10, 1, 1); + ExpiryChecker checker(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); 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(10, 1, 1, 1); + ExpiryChecker checker(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); 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(10, 1, 1, 1); + ExpiryChecker checker(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); 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/CMakeLists.txt b/src/CMakeLists.txt index d040b0fa5..67c97fb61 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,392 +1,394 @@ # SPDX-License-Identifier: CC0-1.0 # SPDX-FileCopyrightText: none # target_include_directories does not handle empty include paths include_directories( ${Boost_INCLUDE_DIRS} ${GPGME_INCLUDES} ) add_definitions(-DTRANSLATION_DOMAIN=\"libkleopatra\") #add_definitions( -DQT_NO_CAST_FROM_ASCII ) #add_definitions( -DQT_NO_CAST_TO_ASCII ) kde_enable_exceptions() add_definitions( -DGPGMEPP_ERR_SOURCE_DEFAULT=13 ) # 13 is GPG_ERR_SOURCE_KLEO, even if gpg-error's too old to know about add_subdirectory( pics ) add_library(KPim${KF_MAJOR_VERSION}Libkleo) add_library(KPim${KF_MAJOR_VERSION}::Libkleo ALIAS KPim${KF_MAJOR_VERSION}Libkleo) ########### next target ############### target_sources(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE kleo/auditlogentry.cpp kleo/auditlogentry.h kleo/checksumdefinition.cpp kleo/checksumdefinition.h kleo/debug.cpp kleo/debug.h kleo/defaultkeyfilter.cpp kleo/defaultkeyfilter.h kleo/defaultkeygenerationjob.cpp kleo/defaultkeygenerationjob.h kleo/docaction.cpp kleo/docaction.h kleo/dn.cpp kleo/dn.h kleo/enum.cpp kleo/enum.h kleo/expirychecker.cpp kleo/expirychecker.h kleo/kconfigbasedkeyfilter.cpp kleo/kconfigbasedkeyfilter.h kleo/keyfilter.h kleo/keyfiltermanager.cpp kleo/keyfiltermanager.h kleo/keygroup.cpp kleo/keygroup.h kleo/keygroupconfig.cpp kleo/keygroupconfig.h kleo/keygroupimportexport.cpp kleo/keygroupimportexport.h kleo/keyresolver.cpp kleo/keyresolver.h kleo/keyresolvercore.cpp kleo/keyresolvercore.h kleo/keyserverconfig.cpp kleo/keyserverconfig.h kleo/kleoexception.cpp kleo/kleoexception.h kleo/oidmap.cpp kleo/oidmap.h kleo/predicates.h kleo/stl_util.h models/keycache.cpp models/keycache.h models/keycache_p.h models/keylist.h models/keylistmodel.cpp models/keylistmodel.h models/keylistmodelinterface.cpp models/keylistmodelinterface.h models/keylistsortfilterproxymodel.cpp models/keylistsortfilterproxymodel.h models/keyrearrangecolumnsproxymodel.cpp models/keyrearrangecolumnsproxymodel.h models/subkeylistmodel.cpp models/subkeylistmodel.h models/useridlistmodel.cpp models/useridlistmodel.h utils/algorithm.h utils/assuan.cpp utils/assuan.h + utils/chrono.h utils/classify.cpp utils/classify.h utils/compat.cpp utils/compat.h utils/compliance.cpp utils/compliance.h utils/cryptoconfig.cpp utils/cryptoconfig.h utils/cryptoconfig_p.h utils/filesystemwatcher.cpp utils/filesystemwatcher.h utils/formatting.cpp utils/formatting.h utils/gnupg-registry.c utils/gnupg-registry.h utils/gnupg.cpp utils/gnupg.h utils/hex.cpp utils/hex.h utils/keyhelpers.cpp utils/keyhelpers.h utils/qtstlhelpers.cpp utils/qtstlhelpers.h utils/scdaemon.cpp utils/scdaemon.h utils/stringutils.cpp utils/stringutils.h utils/systeminfo.cpp utils/systeminfo.h utils/test.cpp utils/test.h utils/uniquelock.cpp utils/uniquelock.h ) ecm_qt_declare_logging_category(KPim${KF_MAJOR_VERSION}Libkleo HEADER libkleo_debug.h IDENTIFIER LIBKLEO_LOG CATEGORY_NAME org.kde.pim.libkleo DESCRIPTION "libkleo (kleo_core)" EXPORT LIBKLEO ) target_sources(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE ui/auditlogviewer.cpp ui/auditlogviewer.h ui/cryptoconfigentryreaderport.cpp ui/cryptoconfigentryreaderport_p.h ui/cryptoconfigmodule.cpp ui/cryptoconfigmodule.h ui/cryptoconfigmodule_p.h ui/directoryserviceswidget.cpp ui/directoryserviceswidget.h ui/dnattributeorderconfigwidget.cpp ui/dnattributeorderconfigwidget.h ui/editdirectoryservicedialog.cpp ui/editdirectoryservicedialog.h ui/filenamerequester.cpp ui/filenamerequester.h ui/messagebox.cpp ui/messagebox.h ui/navigatabletreeview.cpp ui/navigatabletreeview.h ui/navigatabletreewidget.cpp ui/navigatabletreewidget.h ui/progressbar.cpp ui/progressbar.h ui/progressdialog.cpp ui/progressdialog.h ui/readerportselection.cpp ui/readerportselection.h ) ecm_qt_declare_logging_category(KPim${KF_MAJOR_VERSION}Libkleo HEADER kleo_ui_debug.h IDENTIFIER KLEO_UI_LOG CATEGORY_NAME org.kde.pim.kleo_ui DESCRIPTION "libkleo (kleo_ui)" OLD_CATEGORY_NAMES log_kleo_ui EXPORT LIBKLEO ) target_sources(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE # make this a separate lib. ui/keyapprovaldialog.cpp ui/keyapprovaldialog.h ui/keylistview.cpp ui/keylistview.h ui/keyrequester.cpp ui/keyrequester.h ui/keyselectioncombo.cpp ui/keyselectioncombo.h ui/keyselectiondialog.cpp ui/keyselectiondialog.h ui/newkeyapprovaldialog.cpp ui/newkeyapprovaldialog.h ) if(MINGW) # we do not care about different signedness of passed pointer arguments set_source_files_properties(utils/gnupg-registry.c PROPERTIES COMPILE_OPTIONS "-Wno-pointer-sign") endif() target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo PUBLIC Gpgmepp PRIVATE Qt::Widgets KF${KF_MAJOR_VERSION}::I18n KF${KF_MAJOR_VERSION}::Completion KF${KF_MAJOR_VERSION}::ConfigCore KF${KF_MAJOR_VERSION}::ConfigWidgets KF${KF_MAJOR_VERSION}::CoreAddons KF${KF_MAJOR_VERSION}::WidgetsAddons KF${KF_MAJOR_VERSION}::ItemModels KF${KF_MAJOR_VERSION}::Codecs) if (QT_MAJOR_VERSION STREQUAL "6") target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE Qt6::Core5Compat PUBLIC QGpgmeQt6) else() target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo PUBLIC QGpgme) endif() # Boost::headers may not be available for old versions of Boost if (TARGET Boost::headers) target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE Boost::headers) endif() if (KPim${KF_MAJOR_VERSION}TextEdit_FOUND) add_definitions(-DHAVE_PIMTEXTEDIT) target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE KPim${KF_MAJOR_VERSION}::PimTextEdit) endif() if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) set_target_properties(KPim${KF_MAJOR_VERSION}Libkleo PROPERTIES UNITY_BUILD ON) endif() ecm_generate_export_header(KPim${KF_MAJOR_VERSION}Libkleo BASE_NAME kleo VERSION ${PIM_VERSION} DEPRECATED_BASE_VERSION 0 DEPRECATION_VERSIONS 5.23 ) if(WIN32) target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo ${GPGME_VANILLA_LIBRARIES} ) endif() set_target_properties(KPim${KF_MAJOR_VERSION}Libkleo PROPERTIES VERSION ${LIBKLEO_VERSION} SOVERSION ${LIBKLEO_SOVERSION} EXPORT_NAME Libkleo ) install(TARGETS KPim${KF_MAJOR_VERSION}Libkleo EXPORT KPim${KF_MAJOR_VERSION}LibkleoTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) target_include_directories(KPim${KF_MAJOR_VERSION}Libkleo INTERFACE "$") target_include_directories(KPim${KF_MAJOR_VERSION}Libkleo PUBLIC "$") ecm_generate_headers(libkleo_CamelCase_HEADERS HEADER_NAMES AuditLogEntry ChecksumDefinition Debug DefaultKeyFilter DefaultKeyGenerationJob DocAction Dn Enum ExpiryChecker KConfigBasedKeyFilter KeyFilter KeyFilterManager KeyGroup KeyGroupConfig KeyGroupImportExport KeyResolver KeyResolverCore KeyserverConfig KleoException OidMap Predicates Stl_Util REQUIRED_HEADERS libkleo_HEADERS PREFIX Libkleo RELATIVE kleo ) ecm_generate_headers(libkleo_CamelCase_models_HEADERS HEADER_NAMES KeyCache KeyList KeyListModel KeyListModelInterface KeyListSortFilterProxyModel KeyRearrangeColumnsProxyModel SubkeyListModel UserIDListModel REQUIRED_HEADERS libkleo_models_HEADERS PREFIX Libkleo RELATIVE models ) ecm_generate_headers(libkleo_CamelCase_utils_HEADERS HEADER_NAMES Algorithm Assuan + Chrono Classify Compat Compliance CryptoConfig FileSystemWatcher Formatting GnuPG Hex KeyHelpers QtStlHelpers SCDaemon StringUtils SystemInfo Test UniqueLock REQUIRED_HEADERS libkleo_utils_HEADERS PREFIX Libkleo RELATIVE utils ) ecm_generate_headers(libkleo_CamelCase_ui_HEADERS HEADER_NAMES AuditLogViewer CryptoConfigModule DNAttributeOrderConfigWidget DirectoryServicesWidget EditDirectoryServiceDialog FileNameRequester KeyApprovalDialog KeyListView KeyRequester KeySelectionCombo KeySelectionDialog MessageBox NavigatableTreeView NavigatableTreeWidget NewKeyApprovalDialog ProgressDialog ReaderPortSelection REQUIRED_HEADERS libkleo_ui_HEADERS PREFIX Libkleo RELATIVE ui ) if (QT_MAJOR_VERSION STREQUAL "6") ecm_generate_pri_file(BASE_NAME Libkleo LIB_NAME KPim${KF_MAJOR_VERSION}Libkleo DEPS "QGpgme" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/Libkleo ) else() ecm_generate_pri_file(BASE_NAME Libkleo LIB_NAME KPim${KF_MAJOR_VERSION}Libkleo DEPS "QGpgmeQt6" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/Libkleo ) endif() install(FILES ${libkleo_CamelCase_HEADERS} ${libkleo_CamelCase_models_HEADERS} ${libkleo_CamelCase_ui_HEADERS} ${libkleo_CamelCase_utils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/Libkleo/Libkleo COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kleo_export.h ${libkleo_HEADERS} ${libkleo_models_HEADERS} ${libkleo_ui_HEADERS} ${libkleo_utils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/Libkleo/libkleo COMPONENT Devel ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) if ( WIN32 ) install ( FILES libkleopatrarc-win32.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) else () install ( FILES libkleopatrarc.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) endif () if (BUILD_QCH) ecm_add_qch( KPim${KF_MAJOR_VERSION}Libkleo_QCH NAME KPim${KF_MAJOR_VERSION}Libkleo BASE_NAME KPim${KF_MAJOR_VERSION}Libkleo VERSION ${PIM_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${libkleo_HEADERS} ${libkleo_models_HEADERS} ${libkleo_ui_HEADERS} ${libkleo_utils_HEADERS} #MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" #IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics" LINK_QCHS Qt${QT_MAJOR_VERSION}Core_QCH Qt${QT_MAJOR_VERSION}Gui_QCH Qt${QT_MAJOR_VERSION}Widgets_QCH INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR} BLANK_MACROS KLEO_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() diff --git a/src/kleo/expirychecker.cpp b/src/kleo/expirychecker.cpp index 52f0bf6fe..d0b658505 100644 --- a/src/kleo/expirychecker.cpp +++ b/src/kleo/expirychecker.cpp @@ -1,395 +1,474 @@ /* 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 #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) : q{qq} { } - Q_REQUIRED_RESULT double calculateSecsTillExpiry(const GpgME::Subkey &key) const; + Expiration calculateExpiration(const GpgME::Subkey &key) const; void checkKeyNearExpiry(const GpgME::Key &key, bool isOwnKey, bool isSigningKey, bool ca = false, int recur_limit = 100, const GpgME::Key &orig_key = GpgME::Key::null); - int encryptOwnKeyNearExpiryWarningThreshold; - int encryptKeyNearExpiryWarningThreshold; - int encryptRootCertNearExpiryWarningThreshold; - int encryptChainCertNearExpiryWarningThreshold; - + Kleo::chrono::days ownKeyThreshold; + Kleo::chrono::days otherKeyThreshold; + Kleo::chrono::days rootCertThreshold; + Kleo::chrono::days chainCertThreshold; std::set alreadyWarnedFingerprints; - std::shared_ptr timeProvider; }; -ExpiryChecker::ExpiryChecker(int encrOwnKeyNearExpiryThresholdDays, - int encrKeyNearExpiryThresholdDays, - int encrRootCertNearExpiryThresholdDays, - int encrChainCertNearExpiryThresholdDays) +ExpiryChecker::ExpiryChecker(Kleo::chrono::days ownKeyThreshold, + Kleo::chrono::days otherKeyThreshold, + Kleo::chrono::days rootCertThreshold, + Kleo::chrono::days chainCertThreshold) : d{new ExpiryCheckerPrivate{this}} { - d->encryptOwnKeyNearExpiryWarningThreshold = encrOwnKeyNearExpiryThresholdDays; - d->encryptKeyNearExpiryWarningThreshold = encrKeyNearExpiryThresholdDays; - d->encryptRootCertNearExpiryWarningThreshold = encrRootCertNearExpiryThresholdDays; - d->encryptChainCertNearExpiryWarningThreshold = encrChainCertNearExpiryThresholdDays; + d->ownKeyThreshold = ownKeyThreshold; + d->otherKeyThreshold = otherKeyThreshold; + d->rootCertThreshold = rootCertThreshold; + d->chainCertThreshold = chainCertThreshold; } ExpiryChecker::~ExpiryChecker() = default; -int ExpiryChecker::encryptOwnKeyNearExpiryWarningThresholdInDays() const +Kleo::chrono::days ExpiryChecker::ownKeyThreshold() const { - return d->encryptOwnKeyNearExpiryWarningThreshold; + return d->ownKeyThreshold; } -int ExpiryChecker::encryptKeyNearExpiryWarningThresholdInDays() const +Kleo::chrono::days ExpiryChecker::otherKeyThreshold() const { - return d->encryptKeyNearExpiryWarningThreshold; + return d->otherKeyThreshold; } -int ExpiryChecker::encryptRootCertNearExpiryWarningThresholdInDays() const +Kleo::chrono::days ExpiryChecker::rootCertThreshold() const { - return d->encryptRootCertNearExpiryWarningThreshold; + return d->rootCertThreshold; } -int ExpiryChecker::encryptChainCertNearExpiryWarningThresholdInDays() const +Kleo::chrono::days ExpiryChecker::chainCertThreshold() const { - return d->encryptChainCertNearExpiryWarningThreshold; + return d->chainCertThreshold; } -QString formatOpenPGPMessage(const GpgME::Key &key, int secsTillExpiry, bool isOwnKey, bool isSigningKey) +QString formatOpenPGPMessage(const GpgME::Key &key, Expiration expiration, bool isOwnKey, bool isSigningKey) { - KLocalizedString msg; - static const double secsPerDay = 24 * 60 * 60; - const int daysTillExpiry = 1 + int(abs(secsTillExpiry) / secsPerDay); - if (secsTillExpiry <= 0) { - qCDebug(LIBKLEO_LOG) << "Key 0x" << key.keyID() << " expired " << daysTillExpiry << " days ago"; + 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 (KeyID 0x%3)

" - "

expired less than a day ago.

", - "

Your OpenPGP signing key

%2 (KeyID 0x%3)

" + "

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 (KeyID 0x%3)

" - "

expired less than a day ago.

", - "

Your OpenPGP encryption key

%2 (KeyID 0x%3)

" + "

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 (KeyID 0x%3)

" - "

expired less than a day ago.

", - "

The OpenPGP key for

%2 (KeyID 0x%3)

" + "

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 { - qCDebug(LIBKLEO_LOG) << "Key 0x" << key.keyID() << " expires in less than " << daysTillExpiry << " days"; - if (isSigningKey) { - msg = ki18np( - "

Your OpenPGP signing key

%2 (KeyID 0x%3)

" - "

expires in less than a day.

", - "

Your OpenPGP signing key

%2 (KeyID 0x%3)

" - "

expires in less than %1 days.

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

Your OpenPGP encryption key

%2 (KeyID 0x%3)

" - "

expires in less than a day.

", - "

Your OpenPGP encryption key

%2 (KeyID 0x%3)

" - "

expires in less than %1 days.

"); - } else { - msg = ki18np( - "

The OpenPGP key for

%2 (KeyID 0x%3)

" - "

expires in less than a day.

", - "

The OpenPGP key for

%2 (KeyID 0x%3)

" - "

expires in less than %1 days.

"); - } + 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(daysTillExpiry).subs(QString::fromUtf8(key.userID(0).id())).subs(QString::fromLatin1(key.keyID())).toString(); + return msg.subs(expiration.duration.count() + 1).subs(keyInfo).toString(); } -QString formatSMIMEMessage(const GpgME::Key &key, const GpgME::Key &orig_key, int secsTillExpiry, bool isOwnKey, bool isSigningKey, bool ca) +QString formatSMIMEMessage(const GpgME::Key &key, const GpgME::Key &orig_key, Expiration expiration, bool isOwnKey, bool isSigningKey, bool ca) { - KLocalizedString msg; - static const double secsPerDay = 24 * 60 * 60; - const int daysTillExpiry = 1 + int(abs(secsTillExpiry) / secsPerDay); - if (secsTillExpiry <= 0) { - qCDebug(LIBKLEO_LOG) << "Key 0x" << key.keyID() << " expired " << daysTillExpiry << " days ago"; + 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

%4

" - "

for your S/MIME signing certificate

%2 (serial number %3)

" - "

expired less than a day ago.

", - "

The root certificate

%4

" - "

for your S/MIME signing certificate

%2 (serial number %3)

" + "

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

%4

" - "

for your S/MIME encryption certificate

%2 (serial number %3)

" - "

expired less than a day ago.

", - "

The root certificate

%4

" - "

for your S/MIME encryption certificate

%2 (serial number %3)

" + "

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

%4

" - "

for S/MIME certificate

%2 (serial number %3)

" - "

expired less than a day ago.

", - "

The root certificate

%4

" - "

for S/MIME certificate

%2 (serial number %3)

" + "

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

%4

" - "

for your S/MIME signing certificate

%2 (serial number %3)

" + "

The intermediate CA certificate

%3

" + "

for your S/MIME signing certificate

%2

" "

expired less than a day ago.

", - "

The intermediate CA certificate

%4

" - "

for your S/MIME signing certificate

%2 (serial number %3)

" + "

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

%4

" - "

for your S/MIME encryption certificate

%2 (serial number %3)

" + "

The intermediate CA certificate

%3

" + "

for your S/MIME encryption certificate

%2

" "

expired less than a day ago.

", - "

The intermediate CA certificate

%4

" - "

for your S/MIME encryption certificate

%2 (serial number %3)

" + "

The intermediate CA certificate

%3

" + "

for your S/MIME encryption certificate

%2

" "

expired %1 days ago.

"); } else { msg = ki18np( - "

The intermediate CA certificate

%4

" - "

for S/MIME certificate

%2 (serial number %3)

" + "

The intermediate CA certificate

%3

" + "

for S/MIME certificate

%2

" "

expired less than a day ago.

", - "

The intermediate CA certificate

%4

" - "

for S/MIME certificate

%2 (serial number %3)

" + "

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(); } - return msg.subs(daysTillExpiry) - .subs(Kleo::DN(orig_key.userID(0).id()).prettyDN()) - .subs(QString::fromLatin1(orig_key.issuerSerial())) - .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 (serial number %3)

" + "

Your S/MIME signing certificate

%2

" "

expired less than a day ago.

", - "

Your S/MIME signing certificate

%2 (serial number %3)

" + "

Your S/MIME signing certificate

%2

" "

expired %1 days ago.

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

Your S/MIME encryption certificate

%2 (serial number %3)

" + "

Your S/MIME encryption certificate

%2

" "

expired less than a day ago.

", - "

Your S/MIME encryption certificate

%2 (serial number %3)

" + "

Your S/MIME encryption certificate

%2

" "

expired %1 days ago.

"); } else { msg = ki18np( - "

The S/MIME certificate for

%2 (serial number %3)

" + "

The S/MIME certificate for

%2

" "

expired less than a day ago.

", - "

The S/MIME certificate for

%2 (serial number %3)

" + "

The S/MIME certificate for

%2

" "

expired %1 days ago.

"); } - return msg.subs(daysTillExpiry).subs(Kleo::DN(key.userID(0).id()).prettyDN()).subs(QString::fromLatin1(key.issuerSerial())).toString(); + return msg.subs(expiration.duration.count()).subs(userCertInfo).toString(); } - } else { - qCDebug(LIBKLEO_LOG) << "Key 0x" << key.keyID() << " expires in less than " << daysTillExpiry << " days"; - if (ca) { - if (key.isRoot()) { - if (isSigningKey) { - msg = ki18np( - "

The root certificate

%4

" - "

for your S/MIME signing certificate

%2 (serial number %3);

" - "

expires in less than a day.

", - "

The root certificate

%4

" - "

for your S/MIME signing certificate

%2 (serial number %3);

" - "

expires in less than %1 days.

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

The root certificate

%4

" - "

for your S/MIME encryption certificate

%2 (serial number %3);

" - "

expires in less than a day.

", - "

The root certificate

%4

" - "

for your S/MIME encryption certificate

%2 (serial number %3);

" - "

expires in less than %1 days.

"); - } else { - msg = ki18np( - "

The root certificate

%4

" - "

for S/MIME certificate

%2 (serial number %3);

" - "

expires in less than a day.

", - "

The root certificate

%4

" - "

for S/MIME certificate

%2 (serial number %3);

" - "

expires in less than %1 days.

"); - } + } + 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 { - if (isSigningKey) { - msg = ki18np( - "

The intermediate CA certificate

%4

" - "

for your S/MIME signing certificate

%2 (serial number %3);

" - "

expires in less than a day.

", - "

The intermediate CA certificate

%4

" - "

for your S/MIME signing certificate

%2 (serial number %3);

" - "

expires in less than %1 days.

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

The intermediate CA certificate

%4

" - "

for your S/MIME encryption certificate

%2 (serial number %3);

" - "

expires in less than a day.

", - "

The intermediate CA certificate

%4

" - "

for your S/MIME encryption certificate

%2 (serial number %3);

" - "

expires in less than %1 days.

"); - } else { - msg = ki18np( - "

The intermediate CA certificate

%4

" - "

for S/MIME certificate

%2 (serial number %3);

" - "

expires in less than a day.

", - "

The intermediate CA certificate

%4

" - "

for S/MIME certificate

%2 (serial number %3);

" - "

expires in less than %1 days.

"); - } + 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.

"); } - return msg.subs(daysTillExpiry) - .subs(Kleo::DN(orig_key.userID(0).id()).prettyDN()) - .subs(QString::fromLatin1(orig_key.issuerSerial())) - .subs(Kleo::DN(key.userID(0).id()).prettyDN()) - .toString(); } else { if (isSigningKey) { msg = ki18np( - "

Your S/MIME signing certificate

%2 (serial number %3);

" + "

The intermediate CA certificate

%3

" + "

for your S/MIME signing certificate

%2;

" "

expires in less than a day.

", - "

Your S/MIME signing certificate

%2 (serial number %3);

" + "

The intermediate CA certificate

%3

" + "

for your S/MIME signing certificate

%2;

" "

expires in less than %1 days.

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

Your S/MIME encryption certificate

%2 (serial number %3);

" + "

The intermediate CA certificate

%3

" + "

for your S/MIME encryption certificate

%2;

" "

expires in less than a day.

", - "

Your S/MIME encryption certificate

%2 (serial number %3);

" + "

The intermediate CA certificate

%3

" + "

for your S/MIME encryption certificate

%2;

" "

expires in less than %1 days.

"); } else { msg = ki18np( - "

The S/MIME certificate for

%2 (serial number %3);

" + "

The intermediate CA certificate

%3

" + "

for S/MIME certificate

%2;

" "

expires in less than a day.

", - "

The S/MIME certificate for

%2 (serial number %3);

" + "

The intermediate CA certificate

%3

" + "

for S/MIME certificate

%2;

" "

expires in less than %1 days.

"); } - return msg.subs(daysTillExpiry).subs(Kleo::DN(key.userID(0).id()).prettyDN()).subs(QString::fromLatin1(key.issuerSerial())).toString(); } + 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(); } -double ExpiryCheckerPrivate::calculateSecsTillExpiry(const GpgME::Subkey &key) const +Expiration ExpiryCheckerPrivate::calculateExpiration(const GpgME::Subkey &subkey) const { - const auto t = timeProvider ? timeProvider->getTime() : std::time(nullptr); - return std::difftime(key.expirationTime(), t); + 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) { 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()); - if (subkey.neverExpires()) { + const auto expiration = calculateExpiration(subkey); + if (expiration.status == Expiration::NeverExpires) { return; } - static const double secsPerDay = 24 * 60 * 60; - const double secsTillExpiry = calculateSecsTillExpiry(subkey); - if (secsTillExpiry <= 0) { - const QString msg = key.protocol() == GpgME::OpenPGP ? formatOpenPGPMessage(key, secsTillExpiry, isOwnKey, isSigningKey) - : formatSMIMEMessage(key, orig_key, secsTillExpiry, isOwnKey, isSigningKey, ca); + 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); alreadyWarnedFingerprints.insert(subkey.fingerprint()); Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyExpired : ExpiryChecker::OtherKeyExpired, newMessage); } else { - const int daysTillExpiry = 1 + int(secsTillExpiry / secsPerDay); - const int threshold = ca ? (key.isRoot() ? encryptRootCertNearExpiryWarningThreshold : encryptChainCertNearExpiryWarningThreshold) - : (isOwnKey ? encryptOwnKeyNearExpiryWarningThreshold : encryptKeyNearExpiryWarningThreshold); - if (threshold > -1 && daysTillExpiry <= threshold) { - const QString msg = key.protocol() == GpgME::OpenPGP ? formatOpenPGPMessage(key, secsTillExpiry, isOwnKey, isSigningKey) - : formatSMIMEMessage(key, orig_key, secsTillExpiry, isOwnKey, isSigningKey, ca); + const auto threshold = ca ? (key.isRoot() ? rootCertThreshold : chainCertThreshold) : (isOwnKey ? ownKeyThreshold : 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); 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); } } } } void ExpiryChecker::checkOwnSigningKey(const GpgME::Key &key) 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); } void ExpiryChecker::setTimeProviderForTest(const std::shared_ptr &timeProvider) { d->timeProvider = timeProvider; } diff --git a/src/kleo/expirychecker.h b/src/kleo/expirychecker.h index c02e5c2c0..e40b06fb2 100644 --- a/src/kleo/expirychecker.h +++ b/src/kleo/expirychecker.h @@ -1,76 +1,78 @@ /* 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 KLEO_EXPORT TimeProvider { public: virtual ~TimeProvider() = default; virtual time_t getTime() const = 0; }; class KLEO_EXPORT ExpiryChecker : public QObject { Q_OBJECT public: - explicit ExpiryChecker(int encrOwnKeyNearExpiryThresholdDays, - int encrKeyNearExpiryThresholdDays, - int encrRootCertNearExpiryThresholdDays, - int encrChainCertNearExpiryThresholdDays); + ExpiryChecker(Kleo::chrono::days ownKeyThreshold, + Kleo::chrono::days otherKeyThreshold, + Kleo::chrono::days rootCertThreshold, + Kleo::chrono::days chainCertThreshold); ~ExpiryChecker() override; - Q_REQUIRED_RESULT int encryptOwnKeyNearExpiryWarningThresholdInDays() const; - Q_REQUIRED_RESULT int encryptKeyNearExpiryWarningThresholdInDays() const; - Q_REQUIRED_RESULT int encryptRootCertNearExpiryWarningThresholdInDays() const; - Q_REQUIRED_RESULT int encryptChainCertNearExpiryWarningThresholdInDays() const; + Q_REQUIRED_RESULT Kleo::chrono::days ownKeyThreshold() const; + Q_REQUIRED_RESULT Kleo::chrono::days otherKeyThreshold() const; + Q_REQUIRED_RESULT Kleo::chrono::days rootCertThreshold() const; + Q_REQUIRED_RESULT Kleo::chrono::days chainCertThreshold() 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; 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_METATYPE(GpgME::Key) diff --git a/src/utils/chrono.h b/src/utils/chrono.h new file mode 100644 index 000000000..0e29c8dee --- /dev/null +++ b/src/utils/chrono.h @@ -0,0 +1,19 @@ +/* + utils/chrono.h + + This file is part of libkleopatra, the KDE keymanagement library + SPDX-FileCopyrightText: 2023 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include + +namespace Kleo::chrono +{ +// typedef for duration in days (defined in C++20) +using days = std::chrono::duration>; +}