keepassxc

Форк
0
/
WindowsHello.cpp 
196 строк · 6.6 Кб
1
/*
2
 *  Copyright (C) 2022 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 "WindowsHello.h"
19

20
#include <Userconsentverifierinterop.h>
21
#include <winrt/base.h>
22
#include <winrt/windows.foundation.h>
23
#include <winrt/windows.security.credentials.h>
24
#include <winrt/windows.security.cryptography.h>
25
#include <winrt/windows.storage.streams.h>
26

27
#include "core/AsyncTask.h"
28
#include "crypto/CryptoHash.h"
29
#include "crypto/Random.h"
30
#include "crypto/SymmetricCipher.h"
31

32
#include <QTimer>
33
#include <QWindow>
34

35
using namespace winrt;
36
using namespace Windows::Foundation;
37
using namespace Windows::Security::Credentials;
38
using namespace Windows::Security::Cryptography;
39
using namespace Windows::Storage::Streams;
40

41
namespace
42
{
43
    const std::wstring s_winHelloKeyName{L"keepassxc_winhello"};
44
    int g_promptFocusCount = 0;
45

46
    void queueSecurityPromptFocus(int delay = 500)
47
    {
48
        QTimer::singleShot(delay, [] {
49
            auto hWnd = ::FindWindowA("Credential Dialog Xaml Host", nullptr);
50
            if (hWnd) {
51
                ::SetForegroundWindow(hWnd);
52
            } else if (++g_promptFocusCount <= 3) {
53
                queueSecurityPromptFocus();
54
                return;
55
            }
56
            g_promptFocusCount = 0;
57
        });
58
    }
59

60
    bool deriveEncryptionKey(QByteArray& challenge, QByteArray& key, QString& error)
61
    {
62
        error.clear();
63
        auto challengeBuffer = CryptographicBuffer::CreateFromByteArray(
64
            array_view<uint8_t>(reinterpret_cast<uint8_t*>(challenge.data()), challenge.size()));
65

66
        return AsyncTask::runAndWaitForFuture([&] {
67
            try {
68
                // The first time this is used a key-pair will be generated using the common name
69
                auto result = KeyCredentialManager::RequestCreateAsync(s_winHelloKeyName,
70
                                                                       KeyCredentialCreationOption::FailIfExists)
71
                                  .get();
72

73
                if (result.Status() == KeyCredentialStatus::CredentialAlreadyExists) {
74
                    result = KeyCredentialManager::OpenAsync(s_winHelloKeyName).get();
75
                } else if (result.Status() != KeyCredentialStatus::Success) {
76
                    error = QObject::tr("Failed to create Windows Hello credential.");
77
                    return false;
78
                }
79

80
                const auto signature = result.Credential().RequestSignAsync(challengeBuffer).get();
81
                if (signature.Status() != KeyCredentialStatus::Success) {
82
                    if (signature.Status() != KeyCredentialStatus::UserCanceled) {
83
                        error = QObject::tr("Failed to sign challenge using Windows Hello.");
84
                    }
85
                    return false;
86
                }
87

88
                // Use the SHA-256 hash of the challenge signature as the encryption key
89
                const auto response = signature.Result();
90
                CryptoHash hasher(CryptoHash::Sha256);
91
                hasher.addData({reinterpret_cast<const char*>(response.data()), static_cast<int>(response.Length())});
92
                key = hasher.result();
93
                return true;
94
            } catch (winrt::hresult_error const& ex) {
95
                error = QString::fromStdString(winrt::to_string(ex.message()));
96
                return false;
97
            }
98
        });
99
    }
100
} // namespace
101

102
bool WindowsHello::isAvailable() const
103
{
104
    auto task = concurrency::create_task([] { return KeyCredentialManager::IsSupportedAsync().get(); });
105
    return task.get();
106
}
107

108
QString WindowsHello::errorString() const
109
{
110
    return m_error;
111
}
112

113
bool WindowsHello::setKey(const QUuid& dbUuid, const QByteArray& data)
114
{
115
    queueSecurityPromptFocus();
116

117
    // Generate a random challenge that will be signed by Windows Hello
118
    // to create the key. The challenge is also used as the IV.
119
    auto ivSize = SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM);
120
    auto challenge = Random::instance()->randomArray(ivSize);
121
    QByteArray key;
122
    if (!deriveEncryptionKey(challenge, key, m_error)) {
123
        return false;
124
    }
125

126
    // Encrypt the data using AES-256-CBC
127
    SymmetricCipher cipher;
128
    if (!cipher.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, key, challenge)) {
129
        m_error = QObject::tr("Failed to init KeePassXC crypto.");
130
        return false;
131
    }
132
    QByteArray encrypted = data;
133
    if (!cipher.finish(encrypted)) {
134
        m_error = QObject::tr("Failed to encrypt key data.");
135
        return false;
136
    }
137

138
    // Prepend the challenge/IV to the encrypted data
139
    encrypted.prepend(challenge);
140
    m_encryptedKeys.insert(dbUuid, encrypted);
141
    return true;
142
}
143

144
bool WindowsHello::getKey(const QUuid& dbUuid, QByteArray& data)
145
{
146
    data.clear();
147
    if (!hasKey(dbUuid)) {
148
        m_error = QObject::tr("Failed to get Windows Hello credential.");
149
        return false;
150
    }
151

152
    queueSecurityPromptFocus();
153

154
    // Read the previously used challenge and encrypted data
155
    auto ivSize = SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM);
156
    const auto& keydata = m_encryptedKeys.value(dbUuid);
157
    auto challenge = keydata.left(ivSize);
158
    auto encrypted = keydata.mid(ivSize);
159
    QByteArray key;
160

161
    if (!deriveEncryptionKey(challenge, key, m_error)) {
162
        return false;
163
    }
164

165
    // Decrypt the data using the generated key and IV from above
166
    SymmetricCipher cipher;
167
    if (!cipher.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Decrypt, key, challenge)) {
168
        m_error = QObject::tr("Failed to init KeePassXC crypto.");
169
        return false;
170
    }
171

172
    // Store the decrypted data into the passed parameter
173
    data = encrypted;
174
    if (!cipher.finish(data)) {
175
        data.clear();
176
        m_error = QObject::tr("Failed to decrypt key data.");
177
        return false;
178
    }
179

180
    return true;
181
}
182

183
void WindowsHello::reset(const QUuid& dbUuid)
184
{
185
    m_encryptedKeys.remove(dbUuid);
186
}
187

188
bool WindowsHello::hasKey(const QUuid& dbUuid) const
189
{
190
    return m_encryptedKeys.contains(dbUuid);
191
}
192

193
void WindowsHello::reset()
194
{
195
    m_encryptedKeys.clear();
196
}
197

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

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

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

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