diff --git a/autotests/expirycheckertest.cpp b/autotests/expirycheckertest.cpp index abda2a61a..f6388bffd 100644 --- a/autotests/expirycheckertest.cpp +++ b/autotests/expirycheckertest.cpp @@ -1,246 +1,295 @@ /* 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 "../src/kleo/expirychecker_p.h" - #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; +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("difftime"); - QTest::newRow("neverExpire") << testKey("test@kolab.org", GpgME::OpenPGP) << -1; - QTest::newRow("openpgp") << testKey("alice@autocrypt.example", GpgME::OpenPGP) << 2 * 24 * 60 * 60; - QTest::newRow("smime") << testKey("test@example.com", GpgME::CMS) << 2 * 24 * 60 * 60; + 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(int, difftime); + QFETCH(QDate, fakedate); ExpiryChecker checker(1, 1, 1, 1); + checker.setTimeProviderForTest(std::make_shared(fakedate)); QSignalSpy spy(&checker, &ExpiryChecker::expiryMessage); - checker.d->testMode = true; - checker.d->difftime = difftime; 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) + << 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) + 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); - checker.d->testMode = true; - checker.d->difftime = -1; { + ExpiryChecker checker(1, 1, 1, 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); } - checker.d->alreadyWarnedFingerprints.clear(); { + ExpiryChecker checker(1, 1, 1, 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); } - checker.d->alreadyWarnedFingerprints.clear(); { + ExpiryChecker checker(1, 1, 1, 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) + << 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) + 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); - checker.d->testMode = true; - checker.d->difftime = 5 * 24 * 3600; // 5 days + 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); - checker.d->testMode = true; - checker.d->difftime = 5 * 24 * 3600; // 5 days + 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); - checker.d->testMode = true; - checker.d->difftime = 5 * 24 * 3600; // 5 days + 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 62b6ea14c..d040b0fa5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,393 +1,392 @@ # 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/expirychecker_p.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/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 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 49358bc79..52f0bf6fe 100644 --- a/src/kleo/expirychecker.cpp +++ b/src/kleo/expirychecker.cpp @@ -1,361 +1,395 @@ /* 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 "expirychecker_p.h" #include "dn.h" #include #include #include #include #include +#include + +#include + using namespace Kleo; +class Kleo::ExpiryCheckerPrivate +{ + Kleo::ExpiryChecker *q; + +public: + ExpiryCheckerPrivate(ExpiryChecker *qq) + : q{qq} + { + } + + Q_REQUIRED_RESULT double calculateSecsTillExpiry(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; + + std::set alreadyWarnedFingerprints; + + std::shared_ptr timeProvider; +}; + ExpiryChecker::ExpiryChecker(int encrOwnKeyNearExpiryThresholdDays, int encrKeyNearExpiryThresholdDays, int encrRootCertNearExpiryThresholdDays, int encrChainCertNearExpiryThresholdDays) - : d(new ExpiryCheckerPrivate) + : d{new ExpiryCheckerPrivate{this}} { d->encryptOwnKeyNearExpiryWarningThreshold = encrOwnKeyNearExpiryThresholdDays; d->encryptKeyNearExpiryWarningThreshold = encrKeyNearExpiryThresholdDays; d->encryptRootCertNearExpiryWarningThreshold = encrRootCertNearExpiryThresholdDays; d->encryptChainCertNearExpiryWarningThreshold = encrChainCertNearExpiryThresholdDays; } ExpiryChecker::~ExpiryChecker() = default; int ExpiryChecker::encryptOwnKeyNearExpiryWarningThresholdInDays() const { return d->encryptOwnKeyNearExpiryWarningThreshold; } int ExpiryChecker::encryptKeyNearExpiryWarningThresholdInDays() const { return d->encryptKeyNearExpiryWarningThreshold; } int ExpiryChecker::encryptRootCertNearExpiryWarningThresholdInDays() const { return d->encryptRootCertNearExpiryWarningThreshold; } int ExpiryChecker::encryptChainCertNearExpiryWarningThresholdInDays() const { return d->encryptChainCertNearExpiryWarningThreshold; } QString formatOpenPGPMessage(const GpgME::Key &key, int secsTillExpiry, 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"; 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)

" "

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)

" "

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)

" "

expired %1 days ago.

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

"); } } return msg.subs(daysTillExpiry).subs(QString::fromUtf8(key.userID(0).id())).subs(QString::fromLatin1(key.keyID())).toString(); } QString formatSMIMEMessage(const GpgME::Key &key, const GpgME::Key &orig_key, int secsTillExpiry, 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"; if (ca) { if (key.isRoot()) { 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)

" "

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)

" "

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)

" "

expired %1 days ago.

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

The intermediate CA certificate

%4

" "

for your S/MIME signing certificate

%2 (serial number %3)

" "

expired less than a day ago.

", "

The intermediate CA certificate

%4

" "

for your S/MIME signing certificate

%2 (serial number %3)

" "

expired %1 days ago.

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

The intermediate CA certificate

%4

" "

for your S/MIME encryption certificate

%2 (serial number %3)

" "

expired less than a day ago.

", "

The intermediate CA certificate

%4

" "

for your S/MIME encryption certificate

%2 (serial number %3)

" "

expired %1 days ago.

"); } else { msg = ki18np( "

The intermediate CA certificate

%4

" "

for S/MIME certificate

%2 (serial number %3)

" "

expired less than a day ago.

", "

The intermediate CA certificate

%4

" "

for S/MIME certificate

%2 (serial number %3)

" "

expired %1 days ago.

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

" "

expired less than a day ago.

", "

Your S/MIME signing certificate

%2 (serial number %3)

" "

expired %1 days ago.

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

Your S/MIME encryption certificate

%2 (serial number %3)

" "

expired less than a day ago.

", "

Your S/MIME encryption certificate

%2 (serial number %3)

" "

expired %1 days ago.

"); } else { msg = ki18np( "

The S/MIME certificate for

%2 (serial number %3)

" "

expired less than a day ago.

", "

The S/MIME certificate for

%2 (serial number %3)

" "

expired %1 days ago.

"); } return msg.subs(daysTillExpiry).subs(Kleo::DN(key.userID(0).id()).prettyDN()).subs(QString::fromLatin1(key.issuerSerial())).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.

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

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

" "

expires in less than a day.

", "

Your S/MIME signing certificate

%2 (serial number %3);

" "

expires in less than %1 days.

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

Your S/MIME encryption certificate

%2 (serial number %3);

" "

expires in less than a day.

", "

Your S/MIME encryption certificate

%2 (serial number %3);

" "

expires in less than %1 days.

"); } else { msg = ki18np( "

The S/MIME certificate for

%2 (serial number %3);

" "

expires in less than a day.

", "

The S/MIME certificate for

%2 (serial number %3);

" "

expires in less than %1 days.

"); } return msg.subs(daysTillExpiry).subs(Kleo::DN(key.userID(0).id()).prettyDN()).subs(QString::fromLatin1(key.issuerSerial())).toString(); } } } -double ExpiryChecker::calculateSecsTillExpiriy(const GpgME::Subkey &key) const +double ExpiryCheckerPrivate::calculateSecsTillExpiry(const GpgME::Subkey &key) const { - if (d->testMode) { - return d->difftime; - } - - return ::difftime(key.expirationTime(), time(nullptr)); + const auto t = timeProvider ? timeProvider->getTime() : std::time(nullptr); + return std::difftime(key.expirationTime(), t); } -void ExpiryChecker::checkKeyNearExpiry(const GpgME::Key &key, bool isOwnKey, bool isSigningKey, bool ca, int recur_limit, const GpgME::Key &orig_key) const +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 = !d->alreadyWarnedFingerprints.count(subkey.fingerprint()); + const bool newMessage = !alreadyWarnedFingerprints.count(subkey.fingerprint()); if (subkey.neverExpires()) { return; } static const double secsPerDay = 24 * 60 * 60; - const double secsTillExpiry = calculateSecsTillExpiriy(subkey); + 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); - d->alreadyWarnedFingerprints.insert(subkey.fingerprint()); - Q_EMIT expiryMessage(key, msg, isOwnKey ? OwnKeyExpired : OtherKeyExpired, newMessage); + 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() ? encryptRootCertNearExpiryWarningThresholdInDays() : encryptChainCertNearExpiryWarningThresholdInDays()) - : (isOwnKey ? encryptOwnKeyNearExpiryWarningThresholdInDays() : encryptKeyNearExpiryWarningThresholdInDays()); + 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); - d->alreadyWarnedFingerprints.insert(subkey.fingerprint()); - Q_EMIT expiryMessage(key, msg, isOwnKey ? OwnKeyNearExpiry : OtherKeyNearExpiry, newMessage); + 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 { - checkKeyNearExpiry(key, /*isOwnKey*/ true, /*isSigningKey*/ true); + d->checkKeyNearExpiry(key, /*isOwnKey*/ true, /*isSigningKey*/ true); } void ExpiryChecker::checkOwnKey(const GpgME::Key &key) const { - checkKeyNearExpiry(key, /*isOwnKey*/ true, /*isSigningKey*/ false); + d->checkKeyNearExpiry(key, /*isOwnKey*/ true, /*isSigningKey*/ false); } void ExpiryChecker::checkKey(const GpgME::Key &key) const { - checkKeyNearExpiry(key, false, false); + 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 f27679c5c..c02e5c2c0 100644 --- a/src/kleo/expirychecker.h +++ b/src/kleo/expirychecker.h @@ -1,79 +1,76 @@ /* 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 -class ExpiryCheckerTest; - 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: - using Ptr = QSharedPointer; explicit ExpiryChecker(int encrOwnKeyNearExpiryThresholdDays, int encrKeyNearExpiryThresholdDays, int encrRootCertNearExpiryThresholdDays, int encrChainCertNearExpiryThresholdDays); ~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; 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; -private: - friend class ::ExpiryCheckerTest; +public: + void setTimeProviderForTest(const std::shared_ptr &); +private: std::unique_ptr const d; - - Q_REQUIRED_RESULT double calculateSecsTillExpiriy(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) const; }; } Q_DECLARE_METATYPE(GpgME::Key) diff --git a/src/kleo/expirychecker_p.h b/src/kleo/expirychecker_p.h deleted file mode 100644 index 7699f2e3e..000000000 --- a/src/kleo/expirychecker_p.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - SPDX-FileCopyrightText: 2022 Sandro Knauß - - SPDX-License-Identifier: LGPL-2.0-or-later -*/ - -#pragma once - -#include - -#include - -namespace Kleo -{ - -class ExpiryCheckerPrivate -{ -public: - int encryptOwnKeyNearExpiryWarningThreshold; - int encryptKeyNearExpiryWarningThreshold; - int encryptRootCertNearExpiryWarningThreshold; - int encryptChainCertNearExpiryWarningThreshold; - - std::set alreadyWarnedFingerprints; - bool testMode = false; - double difftime = 0; -}; - -}