summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Vrátil <dvratil@kde.org>2016-08-09 12:13:30 (GMT)
committerDaniel Vrátil <dvratil@kde.org>2016-08-09 12:13:39 (GMT)
commita0fc9f8ca19718cc5d521538b91ad5b8a68d0b7c (patch)
tree8556c72d5bf0101dc8af3360b03992ed2c2c5eb2
parent948b219c45f77c2577c6f5214b723a94086117d7 (diff)
Import Classify utilities from Kleopatra
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/utils/classify.cpp449
-rw-r--r--src/utils/classify.h156
4 files changed, 608 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 57505fe..9ef8dbb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,7 +19,7 @@ include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(ECMQtDeclareLoggingCategory)
include(ECMAddTests)
-set(PIM_VERSION "5.3.42")
+set(PIM_VERSION "5.3.43")
set(LIBKLEO_LIB_VERSION ${PIM_VERSION})
set(QT_REQUIRED_VERSION "5.5.0")
set(GPGMEPP_LIB_VERSION "5.3.40")
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 931881a..ce08907 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -86,6 +86,7 @@ set(libkleo_core_SRCS
models/useridlistmodel.cpp
utils/filesystemwatcher.cpp
utils/formatting.cpp
+ utils/classify
)
ecm_qt_declare_logging_category(libkleo_core_SRCS HEADER libkleo_debug.h IDENTIFIER LIBKLEO_LOG CATEGORY_NAME log_libkleo)
@@ -229,6 +230,7 @@ ecm_generate_headers(libkleo_CamelCase_utils_HEADERS
HEADER_NAMES
FileSystemWatcher
Formatting
+ Classify
REQUIRED_HEADERS libkleo_utils_HEADERS
PREFIX Libkleo
RELATIVE utils
diff --git a/src/utils/classify.cpp b/src/utils/classify.cpp
new file mode 100644
index 0000000..07dde23
--- /dev/null
+++ b/src/utils/classify.cpp
@@ -0,0 +1,449 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ utils/classify.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 Klarälvdalens Datakonsult AB
+
+ Kleopatra is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ Kleopatra is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "classify.h"
+
+#include "libkleo_debug.h"
+#include "kleo/checksumdefinition.h"
+
+#include <QString>
+#include <QStringList>
+#include <QFile>
+#include <QFileInfo>
+#include <QtAlgorithms>
+#include <QByteArrayMatcher>
+#include <QMap>
+#include <QRegularExpression>
+
+#include <boost/range.hpp>
+
+#include <gpgme++/data.h>
+#include <qgpgme/dataprovider.h>
+
+#ifdef __GLIBCXX__
+# include <ext/algorithm>
+#endif
+
+#include <functional>
+
+using namespace boost;
+using namespace Kleo::Class;
+
+namespace
+{
+
+const unsigned int ExamineContentHint = 0x8000;
+
+static const struct _classification {
+ char extension[4];
+ unsigned int classification;
+} classifications[] = {
+ // ordered by extension
+ { "arl", CMS | Binary | CertificateRevocationList },
+ { "asc", OpenPGP | Ascii | OpaqueSignature | DetachedSignature | CipherText | AnyCertStoreType | ExamineContentHint },
+ { "cer", CMS | Binary | Certificate },
+ { "crl", CMS | Binary | CertificateRevocationList },
+ { "crt", CMS | Binary | Certificate },
+ { "der", CMS | Binary | Certificate | CertificateRevocationList },
+ { "gpg", OpenPGP | Binary | OpaqueSignature | CipherText | AnyCertStoreType | ExamineContentHint},
+ { "p10", CMS | Ascii | CertificateRequest },
+ { "p12", CMS | Binary | ExportedPSM },
+ { "p7c", CMS | Binary | Certificate },
+ { "p7m", CMS | Binary | CipherText },
+ { "p7s", CMS | Binary | AnySignature },
+ { "pem", CMS | Ascii | AnyType | ExamineContentHint },
+ { "pfx", CMS | Binary | Certificate },
+ { "pgp", OpenPGP | Binary | OpaqueSignature | CipherText | AnyCertStoreType | ExamineContentHint},
+ { "sig", OpenPGP | AnyFormat | DetachedSignature },
+};
+
+static const QMap<GpgME::Data::Type, unsigned int> gpgmeTypeMap {
+ { GpgME::Data::PGPSigned, OpenPGP | OpaqueSignature },
+ /* PGPOther might be just an unencrypted unsigned pgp message. Decrypt
+ * would yield the plaintext anyway so for us this is CipherText. */
+ { GpgME::Data::PGPOther, OpenPGP | CipherText },
+ { GpgME::Data::PGPKey, OpenPGP | Certificate },
+ { GpgME::Data::CMSSigned, CMS | AnySignature },
+ { GpgME::Data::CMSEncrypted, CMS | CipherText },
+ /* See PGPOther */
+ { GpgME::Data::CMSOther, CMS | CipherText },
+ { GpgME::Data::X509Cert, CMS | Certificate},
+ { GpgME::Data::PKCS12, CMS | Binary | ExportedPSM },
+ { GpgME::Data::PGPEncrypted, OpenPGP | CipherText },
+ { GpgME::Data::PGPSignature, OpenPGP | DetachedSignature },
+};
+
+static const unsigned int defaultClassification = NoClass;
+
+template <template <typename U> class Op>
+struct ByExtension {
+ typedef bool result_type;
+
+ template <typename T>
+ bool operator()(const T &lhs, const T &rhs) const
+ {
+ return Op<int>()(qstricmp(lhs.extension, rhs.extension), 0);
+ }
+ template <typename T>
+ bool operator()(const T &lhs, const char *rhs) const
+ {
+ return Op<int>()(qstricmp(lhs.extension, rhs), 0);
+ }
+ template <typename T>
+ bool operator()(const char *lhs, const T &rhs) const
+ {
+ return Op<int>()(qstricmp(lhs, rhs.extension), 0);
+ }
+ bool operator()(const char *lhs, const char *rhs) const
+ {
+ return Op<int>()(qstricmp(lhs, rhs), 0);
+ }
+};
+
+static const struct _content_classification {
+ char content[28];
+ unsigned int classification;
+} content_classifications[] = {
+ { "CERTIFICATE", Certificate },
+ { "ENCRYPTED MESSAGE", CipherText },
+ { "MESSAGE", OpaqueSignature | CipherText },
+ { "PKCS12", ExportedPSM },
+ { "PRIVATE KEY BLOCK", ExportedPSM },
+ { "PUBLIC KEY BLOCK", Certificate },
+ { "SIGNATURE", DetachedSignature },
+ { "SIGNED MESSAGE", ClearsignedMessage | DetachedSignature },
+};
+
+template <template <typename U> class Op>
+struct ByContent {
+ typedef bool result_type;
+
+ const unsigned int N;
+ explicit ByContent(unsigned int n) : N(n) {}
+
+ template <typename T>
+ bool operator()(const T &lhs, const T &rhs) const
+ {
+ return Op<int>()(qstrncmp(lhs.content, rhs.content, N), 0);
+ }
+ template <typename T>
+ bool operator()(const T &lhs, const char *rhs) const
+ {
+ return Op<int>()(qstrncmp(lhs.content, rhs, N), 0);
+ }
+ template <typename T>
+ bool operator()(const char *lhs, const T &rhs) const
+ {
+ return Op<int>()(qstrncmp(lhs, rhs.content, N), 0);
+ }
+ bool operator()(const char *lhs, const char *rhs) const
+ {
+ return Op<int>()(qstrncmp(lhs, rhs, N), 0);
+ }
+};
+
+}
+
+unsigned int Kleo::classify(const QStringList &fileNames)
+{
+ if (fileNames.empty()) {
+ return 0;
+ }
+ unsigned int result = classify(fileNames.front());
+ Q_FOREACH (const QString &fileName, fileNames) {
+ result &= classify(fileName);
+ }
+ return result;
+}
+
+static unsigned int classifyExtension(const QFileInfo fi)
+{
+ const _classification *const it = qBinaryFind(begin(classifications), end(classifications),
+ fi.suffix().toLatin1().constData(),
+ ByExtension<std::less>());
+ if (it != end(classifications))
+ if (!(it->classification & ExamineContentHint)) {
+ return it->classification;
+ }
+
+ return it == end(classifications) ? defaultClassification
+ : it->classification;
+}
+
+unsigned int Kleo::classify(const QString &filename)
+{
+#ifdef __GLIBCXX__
+ assert(__gnu_cxx::is_sorted(begin(classifications), end(classifications), ByExtension<std::less>()));
+#endif
+
+ const QFileInfo fi(filename);
+
+ if (!fi.exists()) {
+ return 0;
+ }
+
+ QFile file(filename);
+ /* The least reliable but always availabe classification */
+ const unsigned int extClass = classifyExtension(fi);
+ if (!GpgME::hasFeature(0, GpgME::BinaryAndFineGrainedIdentify) &&
+ !(extClass & ExamineContentHint)) {
+ /* GpgME's identfiy and our internal Classify were so incomplete
+ * before BinaryAndFineGrainedIdentify that we are better of
+ * to just use the file extension if ExamineContentHint is not set. */
+ qCDebug(LIBKLEO_LOG) << "Classified based only on extension.";
+ return extClass;
+ }
+
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCDebug(LIBKLEO_LOG) << "Failed to open file: " << filename << " for classification.";
+ return extClass;
+ }
+
+ /* More reliable */
+ const unsigned int contentClass = classifyContent(file.read(4096));
+ if (contentClass != defaultClassification) {
+ qCDebug(LIBKLEO_LOG) << "Classified based on content.";
+ return contentClass;
+ }
+
+ /* Probably some X509 Stuff that GpgME in it's wisdom does not handle. Again
+ * file extension is probably more reliable as the last resort. */
+ qCDebug(LIBKLEO_LOG) << "No classification based on content.";
+ return extClass;
+}
+
+static unsigned int classifyContentInteral(const QByteArray &data)
+{
+#ifdef __GLIBCXX__
+ assert(__gnu_cxx::is_sorted(begin(content_classifications), end(content_classifications), ByContent<std::less>(100)));
+#endif
+
+ static const char beginString[] = "-----BEGIN ";
+ static const QByteArrayMatcher beginMatcher(beginString);
+ int pos = beginMatcher.indexIn(data);
+ if (pos < 0) {
+ return defaultClassification;
+ }
+ pos += sizeof beginString - 1;
+
+ const bool pgp = qstrncmp(data.data() + pos, "PGP ", 4) == 0;
+ if (pgp) {
+ pos += 4;
+ }
+
+ const int epos = data.indexOf("-----\n", pos);
+ if (epos < 0) {
+ return defaultClassification;
+ }
+
+ const _content_classification *const cit
+ = qBinaryFind(begin(content_classifications), end(content_classifications),
+ data.data() + pos, ByContent<std::less>(epos - pos));
+
+ if (cit != end(content_classifications)) {
+ return cit->classification | (pgp ? OpenPGP : CMS);
+ }
+ return defaultClassification;
+}
+
+unsigned int Kleo::classifyContent(const QByteArray &data)
+{
+ /* As of Version 1.6.0 GpgME does not distinguish between detached
+ * signatures and signatures. So we prefer kleo's classification and
+ * only use gpgme as fallback.
+ * With newer versions we have a better identify that really inspects
+ * the PGP Packages. Which is by far the most reliable classification.
+ * So this is already used for the default classification. File extensions
+ * and our classifyinternal is only used as a fallback.
+ */
+ if (!GpgME::hasFeature(0, GpgME::BinaryAndFineGrainedIdentify)) {
+ unsigned int ourClassification = classifyContentInteral(data);
+ if (ourClassification != defaultClassification) {
+ return ourClassification;
+ }
+ }
+ QGpgME::QByteArrayDataProvider dp(data);
+ GpgME::Data gpgmeData(&dp);
+ GpgME::Data::Type type = gpgmeData.type();
+
+ return gpgmeTypeMap.value(type, defaultClassification);
+}
+
+QString Kleo::printableClassification(unsigned int classification)
+{
+ QStringList parts;
+ if (classification & CMS) {
+ parts.push_back(QStringLiteral("CMS"));
+ }
+ if (classification & OpenPGP) {
+ parts.push_back(QStringLiteral("OpenPGP"));
+ }
+ if (classification & Binary) {
+ parts.push_back(QStringLiteral("Binary"));
+ }
+ if (classification & Ascii) {
+ parts.push_back(QStringLiteral("Ascii"));
+ }
+ if (classification & DetachedSignature) {
+ parts.push_back(QStringLiteral("DetachedSignature"));
+ }
+ if (classification & OpaqueSignature) {
+ parts.push_back(QStringLiteral("OpaqueSignature"));
+ }
+ if (classification & ClearsignedMessage) {
+ parts.push_back(QStringLiteral("ClearsignedMessage"));
+ }
+ if (classification & CipherText) {
+ parts.push_back(QStringLiteral("CipherText"));
+ }
+ if (classification & Certificate) {
+ parts.push_back(QStringLiteral("Certificate"));
+ }
+ if (classification & ExportedPSM) {
+ parts.push_back(QStringLiteral("ExportedPSM"));
+ }
+ if (classification & CertificateRequest) {
+ parts.push_back(QStringLiteral("CertificateRequest"));
+ }
+ return parts.join(QStringLiteral(", "));
+}
+
+static QString chopped(QString s, unsigned int n)
+{
+ s.chop(n);
+ return s;
+}
+
+/*!
+ \return the data file that corresponds to the signature file \a
+ signatureFileName, or QString(), if no such file can be found.
+*/
+QString Kleo::findSignedData(const QString &signatureFileName)
+{
+ if (!mayBeDetachedSignature(signatureFileName)) {
+ return QString();
+ }
+ const QString baseName = chopped(signatureFileName, 4);
+ return QFile::exists(baseName) ? baseName : QString();
+}
+
+/*!
+ \return all (existing) candiate signature files for \a signedDataFileName
+
+ Note that there can very well be more than one such file, e.g. if
+ the same data file was signed by both CMS and OpenPGP certificates.
+*/
+QStringList Kleo::findSignatures(const QString &signedDataFileName)
+{
+ QStringList result;
+ for (unsigned int i = 0, end = size(classifications); i < end; ++i)
+ if (classifications[i].classification & DetachedSignature) {
+ const QString candiate = signedDataFileName + QLatin1Char('.') + QLatin1String(classifications[i].extension);
+ if (QFile::exists(candiate)) {
+ result.push_back(candiate);
+ }
+ }
+ return result;
+}
+
+/*!
+ \return the (likely) output filename for \a inputFileName, or
+ "inputFileName.out" if none can be determined.
+*/
+QString Kleo::outputFileName(const QString &inputFileName)
+{
+ const QFileInfo fi(inputFileName);
+
+ if (qBinaryFind(begin(classifications), end(classifications),
+ fi.suffix().toLatin1().constData(),
+ ByExtension<std::less>()) == end(classifications)) {
+ return inputFileName + QLatin1String(".out");
+ } else {
+ return chopped(inputFileName, 4);
+ }
+}
+
+/*!
+ \return the commonly used extension for files of type
+ \a classification, or NULL if none such exists.
+*/
+const char *Kleo::outputFileExtension(unsigned int classification, bool usePGPFileExt)
+{
+
+ if (classification & OpenPGP && usePGPFileExt) {
+ return "pgp";
+ }
+
+ for (unsigned int i = 0; i < sizeof classifications / sizeof * classifications; ++i)
+ if ((classifications[i].classification & classification) == classification) {
+ return classifications[i].extension;
+ }
+ return 0;
+}
+
+bool Kleo::isFingerprint(const QString &fpr)
+{
+ static QRegularExpression fprRegex("[0-9a-fA-F]{40}");
+ return fprRegex.match(fpr).hasMatch();
+}
+
+bool Kleo::isChecksumFile(const QString &file)
+{
+ static bool initialized;
+ static QList<QRegExp> patterns;
+ const QFileInfo fi(file);
+ if (!fi.exists()) {
+ return false;
+ }
+ if (!initialized) {
+ Q_FOREACH (const shared_ptr<ChecksumDefinition> &cd, ChecksumDefinition::getChecksumDefinitions()) {
+ if (cd) {
+ Q_FOREACH (const QString &pattern, cd->patterns()) {
+#ifdef Q_OS_WIN
+ patterns << QRegExp(pattern, Qt::CaseInsensitive);
+#else
+ patterns << QRegExp(pattern, Qt::CaseSensitive);
+#endif
+ }
+ }
+ }
+ initialized = true;
+ }
+
+ const QString fileName = fi.fileName();
+ Q_FOREACH (const QRegExp &pattern, patterns) {
+ if (pattern.exactMatch(fileName)) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/src/utils/classify.h b/src/utils/classify.h
new file mode 100644
index 0000000..399fee9
--- /dev/null
+++ b/src/utils/classify.h
@@ -0,0 +1,156 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ utils/classify.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 Klarälvdalens Datakonsult AB
+
+ Kleopatra is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ Kleopatra is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include <gpgme++/global.h>
+
+#ifndef LIBKLEO_CLASSIFY_H
+#define LIBKLEO_CLASSIFY_H
+
+#include <QByteArray>
+
+#include <kleo_export.h>
+
+class QString;
+class QStringList;
+
+namespace Kleo
+{
+
+namespace Class
+{
+enum {
+ NoClass = 0,
+
+ // protocol:
+ CMS = 0x01,
+ OpenPGP = 0x02,
+
+ AnyProtocol = OpenPGP | CMS,
+ ProtocolMask = AnyProtocol,
+
+ // format:
+ Binary = 0x04,
+ Ascii = 0x08,
+
+ AnyFormat = Binary | Ascii,
+ FormatMask = AnyFormat,
+
+ // type:
+ DetachedSignature = 0x010,
+ OpaqueSignature = 0x020,
+ ClearsignedMessage = 0x040,
+
+ AnySignature = DetachedSignature | OpaqueSignature | ClearsignedMessage,
+
+ CipherText = 0x080,
+
+ AnyMessageType = AnySignature | CipherText,
+
+ Importable = 0x100,
+ Certificate = 0x200 | Importable,
+ ExportedPSM = 0x400 | Importable,
+
+ AnyCertStoreType = Certificate | ExportedPSM,
+
+ CertificateRequest = 0x800,
+
+ CertificateRevocationList = 0x1000,
+
+ AnyType = AnyMessageType | AnyCertStoreType | CertificateRequest | CertificateRevocationList,
+ TypeMask = AnyType
+};
+}
+
+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 QString findSignedData(const QString &signatureFileName);
+KLEO_EXPORT QStringList findSignatures(const QString &signedDataFileName);
+KLEO_EXPORT QString outputFileName(const QString &input);
+
+/** Check if a string looks like a fingerprint (SHA1 sum) */
+KLEO_EXPORT bool isFingerprint(const QString &fpr);
+
+/** Check if a filename matches a ChecksumDefinition pattern */
+KLEO_EXPORT bool isChecksumFile(const QString &file);
+
+KLEO_EXPORT const char *outputFileExtension(unsigned int classification, bool usePGPFileExt);
+
+KLEO_EXPORT QString printableClassification(unsigned int classification);
+
+#define make_convenience( What, Mask ) \
+ inline bool is##What( const QString & filename ) { \
+ return ( classify( filename ) & Class::Mask ) == Class::What ; \
+ } \
+ inline bool is##What( const unsigned int classifcation ) { \
+ return ( classifcation & Class::Mask ) == Class::What ; \
+ } \
+ inline bool mayBe##What( const QString & filename ) { \
+ return classify( filename ) & Class::What ; \
+ } \
+ inline bool mayBe##What( const unsigned int classifcation ) { \
+ return classifcation & Class::What ; \
+ }
+
+make_convenience(CMS, ProtocolMask)
+make_convenience(OpenPGP, ProtocolMask)
+
+make_convenience(Binary, FormatMask)
+make_convenience(Ascii, FormatMask)
+
+make_convenience(DetachedSignature, TypeMask)
+make_convenience(OpaqueSignature, TypeMask)
+make_convenience(CipherText, TypeMask)
+make_convenience(AnyMessageType, TypeMask)
+make_convenience(CertificateRevocationList, TypeMask)
+make_convenience(AnyCertStoreType, TypeMask)
+#undef make_convenience
+
+inline GpgME::Protocol findProtocol(const unsigned int classifcation)
+{
+ if (isOpenPGP(classifcation)) {
+ return GpgME::OpenPGP;
+ } else if (isCMS(classifcation)) {
+ return GpgME::CMS;
+ } else {
+ return GpgME::UnknownProtocol;
+ }
+}
+inline GpgME::Protocol findProtocol(const QString &filename)
+{
+ return findProtocol(classify(filename));
+}
+
+}
+
+#endif /* __KLEOPATRA_UISERVER_CLASSIFY_H__ */