Messagelib

searchfullhashjob.cpp
1 /*
2  SPDX-FileCopyrightText: 2016-2020 Laurent Montel <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "searchfullhashjob.h"
8 
9 #include "checkphishingurlutil.h"
10 #include <QNetworkAccessManager>
11 #include <QNetworkConfigurationManager>
12 #include <PimCommon/NetworkManager>
13 #include <QJsonDocument>
14 #include <QUrlQuery>
15 #include <webengineviewer_debug.h>
16 
17 using namespace WebEngineViewer;
18 
19 WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson_SearchFullHashJob = true;
20 
21 class WebEngineViewer::SearchFullHashJobPrivate
22 {
23 public:
24  SearchFullHashJobPrivate()
25  {
26  }
27 
28  Q_REQUIRED_RESULT bool foundExactHash(const QList<QByteArray> &listLongHash);
30  QUrl mUrl;
31  QStringList mDatabaseHashes;
32  QNetworkAccessManager *mNetworkAccessManager = nullptr;
33 };
34 
35 SearchFullHashJob::SearchFullHashJob(QObject *parent)
36  : QObject(parent)
37  , d(new SearchFullHashJobPrivate)
38 {
39  d->mNetworkAccessManager = new QNetworkAccessManager(this);
40  d->mNetworkAccessManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
41  d->mNetworkAccessManager->setStrictTransportSecurityEnabled(true);
42  d->mNetworkAccessManager->enableStrictTransportSecurityStore(true);
43 
44  connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &SearchFullHashJob::slotCheckUrlFinished);
45  connect(d->mNetworkAccessManager, &QNetworkAccessManager::sslErrors, this, &SearchFullHashJob::slotSslErrors);
46 }
47 
48 SearchFullHashJob::~SearchFullHashJob()
49 {
50  delete d;
51 }
52 
53 void SearchFullHashJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)
54 {
55  qCDebug(WEBENGINEVIEWER_LOG) << " void SearchFullHashJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)" << error.count();
56  reply->ignoreSslErrors(error);
57 }
58 
59 void SearchFullHashJob::parse(const QByteArray &replyStr)
60 {
61  /*
62 
63  {
64  "matches": [{
65  "threatType": "MALWARE",
66  "platformType": "WINDOWS",
67  "threatEntryType": "URL",
68  "threat": {
69  "hash": "WwuJdQx48jP-4lxr4y2Sj82AWoxUVcIRDSk1PC9Rf-4="
70  },
71  "threatEntryMetadata": {
72  "entries": [{
73  "key": "bWFsd2FyZV90aHJlYXRfdHlwZQ==", // base64-encoded "malware_threat_type"
74  "value": "TEFORElORw==" // base64-encoded "LANDING"
75  }]
76  },
77  "cacheDuration": "300.000s"
78  }, {
79  "threatType": "SOCIAL_ENGINEERING",
80  "platformType": "WINDOWS",
81  "threatEntryType": "URL",
82  "threat": {
83  "hash": "771MOrRPMn6xPKlCrXx_CrR-wmCk0LgFFoSgGy7zUiA="
84  },
85  "threatEntryMetadata": {
86  "entries": []
87  },
88  "cacheDuration": "300.000s"
89  }],
90  "minimumWaitDuration": "300.000s",
91  "negativeCacheDuration": "300.000s"
92  }
93  */
94  QJsonDocument document = QJsonDocument::fromJson(replyStr);
95  if (document.isNull()) {
96  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
97  } else {
98  const QVariantMap answer = document.toVariant().toMap();
99  if (answer.isEmpty()) {
100  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Ok, d->mUrl);
101  return;
102  } else {
103  const QVariantList info = answer.value(QStringLiteral("matches")).toList();
104  //TODO
105  //const QString minimumWaitDuration = answer.value(QStringLiteral("minimumWaitDuration")).toString();
106  //const QString negativeCacheDuration = answer.value(QStringLiteral("negativeCacheDuration")).toString();
107  //Implement multi match ?
108  if (info.count() == 1) {
109  const QVariantMap map = info.at(0).toMap();
110  const QString threatTypeStr = map[QStringLiteral("threatType")].toString();
111 
112  //const QString cacheDuration = map[QStringLiteral("cacheDuration")].toString();
113 
114  if (threatTypeStr == QLatin1String("MALWARE")) {
115  const QVariantMap urlMap = map[QStringLiteral("threat")].toMap();
116  QList<QByteArray> hashList;
117  QMap<QString, QVariant>::const_iterator urlMapIt = urlMap.cbegin();
118  const QMap<QString, QVariant>::const_iterator urlMapItEnd = urlMap.cend();
119  hashList.reserve(urlMap.count());
120  for (; urlMapIt != urlMapItEnd; ++urlMapIt) {
121  const QByteArray hashStr = urlMapIt.value().toByteArray();
122  hashList << hashStr;
123  }
124 
125  if (d->foundExactHash(hashList)) {
126  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::MalWare, d->mUrl);
127  } else {
128  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
129  }
130  const QVariantMap threatEntryMetadataMap = map[QStringLiteral("threatEntryMetadata")].toMap();
131  if (!threatEntryMetadataMap.isEmpty()) {
132  //TODO
133  }
134  } else {
135  qCWarning(WEBENGINEVIEWER_LOG) << " SearchFullHashJob::parse threatTypeStr : " << threatTypeStr;
136  }
137  } else {
138  qCWarning(WEBENGINEVIEWER_LOG) << " SearchFullHashJob::parse matches multi element : " << info.count();
139  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
140  }
141  }
142  }
143  deleteLater();
144 }
145 
146 bool SearchFullHashJobPrivate::foundExactHash(const QList<QByteArray> &listLongHash)
147 {
148  const QList<QByteArray> lstLongHash = mHashs.keys();
149  for (const QByteArray &ba : lstLongHash) {
150  if (listLongHash.contains(ba)) {
151  return true;
152  }
153  }
154  return false;
155 }
156 
157 void SearchFullHashJob::slotCheckUrlFinished(QNetworkReply *reply)
158 {
159  parse(reply->readAll());
160  reply->deleteLater();
161 }
162 
163 void SearchFullHashJob::setSearchHashs(const QHash<QByteArray, QByteArray> &hash)
164 {
165  d->mHashs = hash;
166 }
167 
168 QByteArray SearchFullHashJob::jsonRequest() const
169 {
170  /*
171  {
172  "client": {
173  "clientId": "yourcompanyname",
174  "clientVersion": "1.5.2"
175  },
176  "clientStates": [
177  "ChAIARABGAEiAzAwMSiAEDABEAE=",
178  "ChAIAhABGAEiAzAwMSiAEDABEOgH"
179  ],
180  "threatInfo": {
181  "threatTypes": ["MALWARE", "SOCIAL_ENGINEERING"],
182  "platformTypes": ["WINDOWS"],
183  "threatEntryTypes": ["URL"],
184  "threatEntries": [
185  {"hash": "WwuJdQ=="},
186  {"hash": "771MOg=="},
187  {"hash": "5eOrwQ=="}
188  ]
189  }
190  }
191  */
192  QVariantMap clientMap;
193  QVariantMap map;
194 
195  clientMap.insert(QStringLiteral("clientId"), QStringLiteral("KDE"));
196  clientMap.insert(QStringLiteral("clientVersion"), CheckPhishingUrlUtil::versionApps());
197  map.insert(QStringLiteral("client"), clientMap);
198 
199  //clientStates We can support multi database.
200  QVariantList clientStatesList;
201  for (const QString &str : qAsConst(d->mDatabaseHashes)) {
202  if (!str.isEmpty()) {
203  clientStatesList.append(str);
204  }
205  }
206  map.insert(QStringLiteral("clientStates"), clientStatesList);
207 
208  QVariantMap threatMap;
209  QVariantList platformList;
210  platformList.append(QLatin1String("WINDOWS"));
211  threatMap.insert(QStringLiteral("platformTypes"), platformList);
212 
213  const QVariantList threatTypesList = { QStringLiteral("MALWARE") };
214  threatMap.insert(QStringLiteral("threatTypes"), threatTypesList);
215  const QVariantList threatEntryTypesList = { QStringLiteral("URL") };
216  threatMap.insert(QStringLiteral("threatEntryTypes"), threatEntryTypesList);
217 
218  QVariantList threatEntriesList;
219 
220  QVariantMap hashUrlMap;
222  while (i.hasNext()) {
223  i.next();
224  hashUrlMap.insert(QStringLiteral("hash"), i.value());
225  }
226  threatEntriesList.append(hashUrlMap);
227 
228  threatMap.insert(QStringLiteral("threatEntries"), threatEntriesList);
229 
230  map.insert(QStringLiteral("threatInfo"), threatMap);
231 
232  const QJsonDocument postData = QJsonDocument::fromVariant(map);
233  const QByteArray baPostData = postData.toJson(webengineview_useCompactJson_SearchFullHashJob ? QJsonDocument::Compact : QJsonDocument::Indented);
234  return baPostData;
235 }
236 
237 void SearchFullHashJob::start()
238 {
239  if (!PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline()) {
240  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork, d->mUrl);
241  deleteLater();
242  } else if (canStart()) {
244  query.addQueryItem(QStringLiteral("key"), WebEngineViewer::CheckPhishingUrlUtil::apiKey());
245  QUrl safeUrl = QUrl(QStringLiteral("https://safebrowsing.googleapis.com/v4/fullHashes:find"));
246  safeUrl.setQuery(query);
247  QNetworkRequest request(safeUrl);
248  request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
249 
250  const QByteArray baPostData = jsonRequest();
251  //qCDebug(WEBENGINEVIEWER_LOG) << " postData.toJson()" << baPostData;
252  Q_EMIT debugJson(baPostData);
253  QNetworkReply *reply = d->mNetworkAccessManager->post(request, baPostData);
254 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
255  connect(reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &SearchFullHashJob::slotError);
256 #else
257  connect(reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &SearchFullHashJob::slotError);
258 #endif
259 
260  } else {
261  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl, d->mUrl);
262  deleteLater();
263  }
264 }
265 
266 void SearchFullHashJob::slotError(QNetworkReply::NetworkError error)
267 {
269  qCWarning(WEBENGINEVIEWER_LOG) << " error " << error << " error string : " << reply->errorString();
270  reply->deleteLater();
271  deleteLater();
272 }
273 
274 bool SearchFullHashJob::canStart() const
275 {
276  return !d->mHashs.isEmpty() && !d->mDatabaseHashes.isEmpty() && !d->mUrl.isEmpty();
277 }
278 
279 void SearchFullHashJob::setDatabaseState(const QStringList &hash)
280 {
281  d->mDatabaseHashes = hash;
282 }
283 
284 void SearchFullHashJob::setSearchFullHashForUrl(const QUrl &url)
285 {
286  d->mUrl = url;
287 }
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
void sslErrors(QNetworkReply *reply, const QList< QSslError > &errors)
QByteArray toJson() const const
std::optional< QSqlQuery > query(const QString &queryStatement)
QString errorString() const const
QObject * sender() const const
void reserve(int alloc)
void ignoreSslErrors(const QList< QSslError > &errors)
QJsonDocument fromVariant(const QVariant &variant)
QByteArray & insert(int i, char ch)
bool isNull() const const
void addQueryItem(const QString &key, const QString &value)
int count(const T &value) const const
QVector< QVariant > parse(const QString &message, const QDate &externalIssueDate=QDate())
QByteArray readAll()
void deleteLater()
QByteArray & append(char ch)
QVariant toVariant() const const
void errorOccurred(QNetworkReply::NetworkError code)
bool contains(const T &value) const const
QMap< QString, QVariant > toMap() const const
int count() const const
void finished(QNetworkReply *reply)
void setQuery(const QString &query, QUrl::ParsingMode mode)
QNetworkReply::NetworkError error() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
QFuture< void > map(Sequence &sequence, MapFunctor function)
Q_EMITQ_EMIT
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Tue Sep 22 2020 23:14:02 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.