diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(KF5_VERSION "5.38.0") find_package(ECM ${KF5_VERSION} CONFIG REQUIRED) -set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules;${ECM_MODULE_PATH}") set(LIBRARY_NAMELINK) include(GenerateExportHeader) include(ECMSetupVersion) @@ -47,6 +47,7 @@ find_package(Boost 1.34.0) set_package_properties(Boost PROPERTIES DESCRIPTION "Boost C++ Libraries" URL "http://www.boost.org" TYPE REQUIRED PURPOSE "Boost is required for building most KDEPIM applications") +find_package(KSBA REQUIRED) set_package_properties(KF5PimTextEdit PROPERTIES DESCRIPTION "A textedit with PIM-specific features." diff --git a/cmake/modules/FindKSBA.cmake b/cmake/modules/FindKSBA.cmake new file mode 100644 --- /dev/null +++ b/cmake/modules/FindKSBA.cmake @@ -0,0 +1,239 @@ +# - Try :to find the KSBA library + +# Variables set: +# KSBA_{INCLUDES,FOUND,LIBRARIES} will be set for each of the above + + +#if this is built-in, please replace, if it isn't, export into a MacroToBool.cmake of it's own +macro( macro_bool_to_bool FOUND_VAR ) + foreach( _current_VAR ${ARGN} ) + if ( ${FOUND_VAR} ) + set( ${_current_VAR} TRUE ) + else() + set( ${_current_VAR} FALSE ) + endif() + endforeach() +endmacro() + + +if ( WIN32 ) + # On Windows, we don't have a ksba-config script, so we need to + # look for the stuff ourselves: + + # in cmake, AND and OR have the same precedence, there's no + # subexpressions, and expressions are evaluated short-circuit'ed + # IOW: CMake if() suxx. + set( _seem_to_have_cached_ksba false ) + if ( KSBA_INCLUDES ) + if ( KSBA_LIBRARIES ) + set( _seem_to_have_cached_ksba true ) + endif() + endif() + + if ( _seem_to_have_cached_ksba ) + macro_bool_to_bool( KSBA_LIBRARIES KSBA_FOUND ) + else() + set( KSBA_FOUND false ) + + find_path( KSBA_INCLUDES ksba.h + ${CMAKE_INCLUDE_PATH} + ${CMAKE_INSTALL_PREFIX}/include + ) + + find_library( _ksba_library NAMES ksba libksba + PATHS + ${CMAKE_LIBRARY_PATH} + ${CMAKE_INSTALL_PREFIX}/lib + ) + + find_library( _gpg_error_library NAMES gpg-error libgpg-error gpg-error-0 libgpg-error-0 + PATHS + ${CMAKE_LIBRARY_PATH} + ${CMAKE_INSTALL_PREFIX}/lib + ) + + set( KSBA_INCLUDES ${KSBA_INCLUDES} ) + + if ( _ksba_library AND _gpg_error_library ) + set( KSBA_LIBRARIES ${_ksba_library} ${_gpg_error_library} ws2_32 ) + set( KSBA_FOUND true ) + endif() + + endif() + + if (KSBA_FOUND) + set (HAVE_KSBA 1) + else() + set (HAVE_KSBA 0) + endif() + +else() # not WIN32 + + # On *nix, we have the ksba-config script which can tell us all we + # need to know: + + # see WIN32 case for an explanation of what this does: + set( _seem_to_have_cached_ksba false ) + if ( KSBA_INCLUDES AND KSBA_LIBRARIES ) + set( _seem_to_have_cached_ksba true ) + endif() + + if ( _seem_to_have_cached_ksba ) + + set( KSBA_FOUND true ) + + else() + + set( KSBA_FOUND false ) + + find_program( _KSBACONFIG_EXECUTABLE NAMES ksba-config ) + + # if ksba-config has been found + if ( _KSBACONFIG_EXECUTABLE ) + + message( STATUS "Found ksba-config at ${_KSBACONFIG_EXECUTABLE}" ) + + exec_program( ${_KSBACONFIG_EXECUTABLE} ARGS --version OUTPUT_VARIABLE KSBA_VERSION ) + + set( _KSBA_MIN_VERSION "1.0.0" ) + if( KSBA_VERSION VERSION_GREATER ${_KSBA_MIN_VERSION} ) + set( _KSBA_INSTALLED_VERSION_OK TRUE ) + endif() + + if ( NOT _KSBA_INSTALLED_VERSION_OK ) + + message( STATUS "The installed version of KSBA is too old: ${KSBA_VERSION} (required: >= ${_KSBA_MIN_VERSION})" ) + + else() + + message( STATUS "Found KSBA v${KSBA_VERSION}" ) + + exec_program( ${_KSBACONFIG_EXECUTABLE} ARGS --libs OUTPUT_VARIABLE _ksba_config_libs RETURN_VALUE _ret ) + if ( _ret ) + set( _ksba_config_libs ) + endif() + + # append -lgpg-error to the list of libraries, if necessary + if ( _ksba_config_libs AND NOT _ksba_config_libs MATCHES "lgpg-error" ) + set( _ksba_config_libs "${_ksba_config_libs} -lgpg-error" ) + endif() + + if ( _ksba_config_libs ) + + exec_program( ${_KSBACONFIG_EXECUTABLE} ARGS --cflags OUTPUT_VARIABLE _KSBA_CFLAGS ) + + if ( _KSBA_CFLAGS ) + string( REGEX REPLACE "(\r?\n)+$" " " _KSBA_CFLAGS "${_KSBA_CFLAGS}" ) + string( REGEX REPLACE " *-I" ";" KSBA_INCLUDES "${_KSBA_CFLAGS}" ) + endif() + + if ( _ksba_config_libs ) + + set( _ksba_library_dirs ) + set( _ksba_library_names ) + + string( REGEX REPLACE " +" ";" _ksba_config_libs "${_ksba_config_libs}" ) + + foreach( _flag ${_ksba_config_libs} ) + if ( "${_flag}" MATCHES "^-L" ) + string( REGEX REPLACE "^-L" "" _dir "${_flag}" ) + file( TO_CMAKE_PATH "${_dir}" _dir ) + set( _ksba_library_dirs ${_ksba_library_dirs} "${_dir}" ) + elseif( "${_flag}" MATCHES "^-l" ) + string( REGEX REPLACE "^-l" "" _name "${_flag}" ) + set( _ksba_library_names ${_ksba_library_names} "${_name}" ) + endif() + endforeach() + + set( KSBA_FOUND true ) + + foreach( _name ${_ksba_library_names} ) + set( _ksba_${_name}_lib ) + + # if -L options were given, look only there + if ( _ksba_library_dirs ) + find_library( _ksba_${_name}_lib NAMES ${_name} PATHS ${_ksba_library_dirs} NO_DEFAULT_PATH ) + endif() + + # if not found there, look in system directories + if ( NOT _ksba_${_name}_lib ) + find_library( _ksba_${_name}_lib NAMES ${_name} ) + endif() + + # if still not found, then the whole flavour isn't found + if ( NOT _ksba_${_name}_lib ) + if ( KSBA_FOUND ) + set( kSBA_FOUND false ) + set( _not_found_reason "dependant library ${_name} wasn't found" ) + endif() + endif() + + set( KSBA_LIBRARIES ${KSBA_LIBRARIES} "${_ksba_${_name}_lib}" ) + endforeach() + + #check_c_library_exists_explicit( ksba ksba_check_version "${_KSBA_CFLAGS}" "${KSBA_LIBRARIES}" KSBA_FOUND ) + if ( KSBA_FOUND ) + message( STATUS " Checking whether KSBA is usable...yes" ) + else() + message( STATUS " Checking whether KSBA is usable...no" ) + message( STATUS " (${_not_found_reason})" ) + endif() + endif() + + # ensure that they are cached + set( KSBA_INCLUDES ${KSBA_INCLUDES} ) + set( KSBA_LIBRARIES ${KSBA_LIBRARIES} ) + + endif() + + endif() + + endif() + + endif() + + if (KSBA_FOUND) + set (HAVE_KSBA 1) + else() + set (HAVE_KSBA 0) + endif() + +endif() # WIN32 | Unix + + +if ( NOT KSBA_FIND_QUIETLY ) + + if ( KSBA_FOUND ) + message( STATUS "Usable KSBA found." ) + message( STATUS " Includes: ${KSBA_INCLUDES}" ) + message( STATUS " Libraries: ${kSBA_LIBRARIES}" ) + else() + message( STATUS "No usable KSBA found." ) + endif() + + if( KSBA_FIND_REQUIRED ) + set( _KSBA_TYPE "REQUIRED" ) + else() + set( _KSBA_TYPE "OPTIONAL" ) + endif() + + if ( WIN32 ) + set( _ksba_homepage "http://www.gpg4win.org" ) + else() + set( _ksba_homepage "http://www.gnupg.org/related_software/libksba" ) + endif() + + set_package_properties(KSBA PROPERTIES DESCRIPTION "KSBA CMS handling library" + URL ${_ksba_homepage} + TYPE ${_KSBA_TYPE} + PURPOSE "Needed for Kleopatra to correctly detect CMS signatures" + ) + +else() + + if ( KSBA_FIND_REQUIRED AND NOT KSBA_FOUND ) + message( FATAL_ERROR "KSBA is required but was not found." ) + endif() + +endif() + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ # target_include_directories does not handle empty include paths -include_directories(${GPGME_INCLUDES}) +include_directories(${GPGME_INCLUDES} ${KSBA_INCLUDES}) add_definitions(-DTRANSLATION_DOMAIN=\"libkleopatra\") @@ -68,15 +68,22 @@ set(kleo_LIB_SRCS ${libkleo_core_SRCS} ${libkleo_ui_SRCS} ${libkleo_ui_common_SRCS}) -set(kleo_LIB_LIBS PUBLIC QGpgme Gpgmepp PRIVATE Qt5::Widgets - KF5::I18n - KF5::Completion - KF5::ConfigCore - KF5::CoreAddons - KF5::WindowSystem - KF5::WidgetsAddons - KF5::ItemModels - KF5::Codecs) +set(kleo_LIB_LIBS +PUBLIC + QGpgme + Gpgmepp +PRIVATE + Qt5::Widgets + KF5::I18n + KF5::Completion + KF5::ConfigCore + KF5::CoreAddons + KF5::WindowSystem + KF5::WidgetsAddons + KF5::ItemModels + KF5::Codecs + ${KSBA_LIBRARIES} +) if (KF5PimTextEdit_FOUND) add_definitions(-DHAVE_PIMTEXTEDIT) diff --git a/src/utils/classify.h b/src/utils/classify.h --- a/src/utils/classify.h +++ b/src/utils/classify.h @@ -93,6 +93,7 @@ KLEO_EXPORT unsigned int classify(const QString &filename); KLEO_EXPORT unsigned int classify(const QStringList &fileNames); KLEO_EXPORT unsigned int classifyContent(const QByteArray &data); +KLEO_EXPORT unsigned int classifyCMSSignature(const QByteArray &data); KLEO_EXPORT QString findSignedData(const QString &signatureFileName); KLEO_EXPORT QStringList findSignatures(const QString &signedDataFileName); diff --git a/src/utils/classify.cpp b/src/utils/classify.cpp --- a/src/utils/classify.cpp +++ b/src/utils/classify.cpp @@ -46,6 +46,7 @@ #include #include +#include #include #include @@ -285,7 +286,74 @@ GpgME::Data gpgmeData(&dp); GpgME::Data::Type type = gpgmeData.type(); - return gpgmeTypeMap.value(type, defaultClassification); + auto classification = gpgmeTypeMap.value(type, defaultClassification); + if (isCMS(classification) && mayBeDetachedSignature(classification)) { + if (auto sigClassification = classifyCMSSignature(data)) { + classification &= ~AnySignature; + classification |= sigClassification; + } + } + + return classification; +} + +namespace { + +struct KSBAReaderDeleter +{ + static inline void cleanup(ksba_reader_t reader) + { + ksba_reader_release(reader); + } +}; + +struct KSBACMSDeleter +{ + static inline void cleanup(ksba_cms_t cms) + { + ksba_cms_release(cms); + } +}; + +} + +unsigned int Kleo::classifyCMSSignature(const QByteArray &data) +{ + ksba_reader_t reader_ptr; + if (ksba_reader_new(&reader_ptr)) { + return 0; + } + const QScopedPointer reader(reader_ptr); + if (ksba_reader_set_mem(reader.data(), data.constData(), data.size())) { + qCWarning(LIBKLEO_LOG) << "KSBA: Failed to set reader memory"; + return 0; + } + + ksba_cms_t cms_ptr; + if (ksba_cms_new(&cms_ptr)) { + return 0; + } + const QScopedPointer cms(cms_ptr); + + ksba_writer_t writer = 0; + if (ksba_cms_set_reader_writer(cms.data(), reader.data(), writer)) { + qCWarning(LIBKLEO_LOG) << "KSBA: Failed to setup reader and writer"; + return 0; + } + + ksba_stop_reason_t stopReason; + do { + if (auto rc = ksba_cms_parse(cms.data(), &stopReason)) { + qCWarning(LIBKLEO_LOG) << "KSBA: Failed to parse CMS signature:" << gpg_strerror(rc); + return 0; + } + if (stopReason == KSBA_SR_NEED_HASH) { + return DetachedSignature; + } + } while (stopReason != KSBA_SR_READY); + + + return OpaqueSignature; // there's no clearsign in CMS } QString Kleo::printableClassification(unsigned int classification)