keepassxc

Форк
0
/
HibpDownloader.cpp 
187 строк · 5.3 Кб
1
/*
2
 *  Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
3
 *
4
 *  This program is free software: you can redistribute it and/or modify
5
 *  it under the terms of the GNU General Public License as published by
6
 *  the Free Software Foundation, either version 2 or (at your option)
7
 *  version 3 of the License.
8
 *
9
 *  This program is distributed in the hope that it will be useful,
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 *  GNU General Public License for more details.
13
 *
14
 *  You should have received a copy of the GNU General Public License
15
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17

18
#include "HibpDownloader.h"
19
#include "core/NetworkManager.h"
20

21
#include <QCryptographicHash>
22
#include <QNetworkReply>
23

24
namespace
25
{
26
    /*
27
     * Return the SHA1 hash of the specified password in upper-case hex.
28
     *
29
     * The result is always exactly 40 characters long.
30
     */
31
    QString sha1Hex(const QString& password)
32
    {
33
        // Get the binary SHA1
34
        const auto sha1 = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
35
        return sha1.toHex().toUpper();
36
    }
37

38
    /*
39
     * Search a password's hash in the output of the HIBP web service.
40
     *
41
     * Returns the number of times the password is found in breaches, or
42
     * 0 if the password is not in the HIBP result.
43
     */
44
    int pwnCount(const QString& password, const QString& hibpResult)
45
    {
46
        // The first 5 characters of the hash are in the URL already,
47
        // the HIBP result contains the remainder
48
        auto pos = hibpResult.indexOf(sha1Hex(password).mid(5));
49
        if (pos < 0) {
50
            return 0;
51
        }
52

53
        // Skip past the sha1 and ':'
54
        pos += 36;
55

56
        // Find where the count ends
57
        auto end = hibpResult.indexOf('\n', pos);
58
        if (end < 0) {
59
            end = hibpResult.size();
60
        }
61

62
        // Extract the count, remove remaining whitespace, and convert to int
63
        return hibpResult.midRef(pos, end - pos).trimmed().toInt();
64
    }
65
} // namespace
66

67
HibpDownloader::HibpDownloader(QObject* parent)
68
    : QObject(parent)
69
{
70
}
71

72
HibpDownloader::~HibpDownloader()
73
{
74
    abort();
75
}
76

77
/*
78
 * Add one password to the list list of passwords to check.
79
 *
80
 * Invoke this function once for every password to check,
81
 * then call validate().
82
 */
83
void HibpDownloader::add(const QString& password)
84
{
85
    if (!m_pwdsToTry.contains(password)) {
86
        m_pwdsToTry << password;
87
    }
88
}
89

90
/*
91
 * Start validating the passwords against HIBP.
92
 */
93
void HibpDownloader::validate()
94
{
95
    for (auto password : m_pwdsToTry) {
96
        // The URL we query is https://api.pwnedpasswords.com/range/XXXXX,
97
        // where XXXXX is the first five bytes of the hex representation of
98
        // the password's SHA1.
99
        const auto url = QString("https://api.pwnedpasswords.com/range/") + sha1Hex(password).left(5);
100

101
        // HIBP requires clients to specify a user agent in the request
102
        // (https://haveibeenpwned.com/API/v3#UserAgent); however, in order
103
        // to minimize the amount of information we expose about ourselves,
104
        // we don't add the KeePassXC version number or platform.
105
        auto request = QNetworkRequest(url);
106
        request.setRawHeader("User-Agent", "KeePassXC");
107

108
        // Finally, submit the request to HIBP.
109
        auto reply = getNetMgr()->get(request);
110
        connect(reply, &QNetworkReply::finished, this, &HibpDownloader::fetchFinished);
111
        connect(reply, &QIODevice::readyRead, this, &HibpDownloader::fetchReadyRead);
112
        m_replies.insert(reply, {password, {}});
113
    }
114

115
    m_pwdsToTry.clear();
116
}
117

118
int HibpDownloader::passwordsToValidate() const
119
{
120
    return m_pwdsToTry.size();
121
}
122

123
int HibpDownloader::passwordsRemaining() const
124
{
125
    return m_replies.size();
126
}
127

128
/*
129
 * Abort the current online activity (if any).
130
 */
131
void HibpDownloader::abort()
132
{
133
    for (auto reply : m_replies.keys()) {
134
        reply->abort();
135
        reply->deleteLater();
136
    }
137
    m_replies.clear();
138
}
139

140
/*
141
 * Called when new data has been loaded from the HIBP server.
142
 */
143
void HibpDownloader::fetchReadyRead()
144
{
145
    const auto reply = qobject_cast<QNetworkReply*>(sender());
146
    auto entry = m_replies.find(reply);
147
    if (entry != m_replies.end()) {
148
        entry->second += reply->readAll();
149
    }
150
}
151

152
/*
153
 * Called after all data has been loaded from the HIBP server.
154
 */
155
void HibpDownloader::fetchFinished()
156
{
157
    const auto reply = qobject_cast<QNetworkReply*>(sender());
158
    const auto entry = m_replies.find(reply);
159
    if (entry == m_replies.end()) {
160
        return;
161
    }
162

163
    // Get result status
164
    const auto ok = reply->error() == QNetworkReply::NoError;
165
    const auto err = reply->errorString();
166

167
    const auto password = entry->first;
168
    const auto hibpReply = entry->second;
169

170
    reply->deleteLater();
171
    m_replies.remove(reply);
172

173
    // If there was an error, assume it's permanent and abort
174
    // (don't process the rest of the password list).
175
    if (!ok) {
176
        auto msg = tr("Online password validation failed") + ":\n" + err;
177
        if (!hibpReply.isEmpty()) {
178
            msg += "\n" + hibpReply;
179
        }
180
        abort();
181
        emit fetchFailed(msg);
182
        return;
183
    }
184

185
    // Current password validated, send the result to the caller
186
    emit hibpResult(password, pwnCount(password, hibpReply));
187
}
188

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.