.clang-format
.clang-tidy
.gitignore
.vimrc
BrowserTab.cpp
BrowserTab.h
BrowserView.cpp
BrowserView.h
CMakeLists.txt
DatabaseManager.cpp
DatabaseManager.h
DownloadBar.cpp
DownloadBar.h
DownloadWidget.cpp
DownloadWidget.h
MainWindow.cpp
MainWindow.h
Makefile
MasterPasswordDialog.cpp
MasterPasswordDialog.h
PasswordHelper.cpp
PasswordHelper.h
README.md
ThemeConfig.h
VaultManager.cpp
VaultManager.h
browser.desktop
browser.qrc
compile_commands.json
main.cpp
VaultManager.cpp
raw
1#include "VaultManager.h"
2#include <QDateTime>
3#include <QDebug>
4#include <QSqlRecord>
5#include "DatabaseManager.h"
6
7VaultManager::VaultManager() {
8 if (sodium_init() < 0) {
9 qCritical() << "Libsodium could not be initialized!";
10 }
11}
12
13VaultManager::~VaultManager() {
14 lock();
15}
16
17bool VaultManager::init() {
18 return true;
19}
20
21bool VaultManager::isInitialized() {
22 return !DatabaseManager::instance().getVaultMeta("encrypted_database_key").isEmpty();
23}
24
25bool VaultManager::setup(const QString &masterPassword) {
26 QByteArray salt(crypto_pwhash_SALTBYTES, 0);
27 randombytes_buf(salt.data(), salt.size());
28
29 QByteArray dek(crypto_aead_xchacha20poly1305_ietf_KEYBYTES, 0);
30 randombytes_buf(dek.data(), dek.size());
31
32 unsigned long long opslimit = crypto_pwhash_OPSLIMIT_INTERACTIVE;
33 unsigned long long memlimit = crypto_pwhash_MEMLIMIT_INTERACTIVE;
34
35 QByteArray kek = deriveKEK(masterPassword, salt, memlimit, opslimit);
36
37 QByteArray nonce(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, 0);
38 randombytes_buf(nonce.data(), nonce.size());
39
40 QByteArray encryptedDek(dek.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES, 0);
41 unsigned long long outLen;
42
43 crypto_aead_xchacha20poly1305_ietf_encrypt((unsigned char *)encryptedDek.data(), &outLen, (unsigned char *)dek.data(), dek.size(), nullptr, 0, nullptr,
44 (unsigned char *)nonce.data(), (unsigned char *)kek.data());
45
46 DatabaseManager::instance().setVaultMeta("argon2_salt", salt);
47 DatabaseManager::instance().setVaultMeta("encrypted_database_key", encryptedDek);
48 DatabaseManager::instance().setVaultMeta("database_key_nonce", nonce);
49 DatabaseManager::instance().setVaultMeta("argon2_memory_cost", QByteArray::number(memlimit));
50 DatabaseManager::instance().setVaultMeta("argon2_time_cost", QByteArray::number(opslimit));
51
52 databaseKey = dek;
53 return true;
54}
55
56bool VaultManager::unlock(const QString &masterPassword) {
57 QByteArray salt = DatabaseManager::instance().getVaultMeta("argon2_salt");
58 QByteArray encryptedDek = DatabaseManager::instance().getVaultMeta("encrypted_database_key");
59 QByteArray nonce = DatabaseManager::instance().getVaultMeta("database_key_nonce");
60 unsigned long long memlimit = DatabaseManager::instance().getVaultMeta("argon2_memory_cost").toULongLong();
61 unsigned long long opslimit = DatabaseManager::instance().getVaultMeta("argon2_time_cost").toULongLong();
62
63 QByteArray kek = deriveKEK(masterPassword, salt, memlimit, opslimit);
64
65 QByteArray dek(crypto_aead_xchacha20poly1305_ietf_KEYBYTES, 0);
66 unsigned long long outLen;
67
68 if (crypto_aead_xchacha20poly1305_ietf_decrypt((unsigned char *)dek.data(), &outLen, nullptr, (unsigned char *)encryptedDek.data(), encryptedDek.size(), nullptr, 0,
69 (unsigned char *)nonce.data(), (unsigned char *)kek.data()) != 0) {
70 return false;
71 }
72
73 databaseKey = dek;
74 return true;
75}
76
77void VaultManager::lock() {
78 sodium_memzero(databaseKey.data(), databaseKey.size());
79 databaseKey.clear();
80}
81
82QByteArray VaultManager::deriveKEK(const QString &password, const QByteArray &salt, unsigned long long memlimit, unsigned long long opslimit) {
83 QByteArray kek(crypto_aead_xchacha20poly1305_ietf_KEYBYTES, 0);
84 QByteArray passwordBytes = password.toUtf8();
85
86 if (crypto_pwhash((unsigned char *)kek.data(), kek.size(), passwordBytes.data(), passwordBytes.size(), (unsigned char *)salt.data(), opslimit, memlimit,
87 crypto_pwhash_ALG_ARGON2ID13) != 0) {
88 qCritical() << "Argon2id failed!";
89 }
90 return kek;
91}
92
93bool VaultManager::savePassword(const QString &origin, const QString &username, const QString &password) {
94 if (!isUnlocked()) {
95 return false;
96 }
97
98 QByteArray nonce(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, 0);
99 randombytes_buf(nonce.data(), nonce.size());
100
101 QByteArray passwordBytes = password.toUtf8();
102 QByteArray ciphertext(passwordBytes.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES, 0);
103 unsigned long long outLen;
104
105 crypto_aead_xchacha20poly1305_ietf_encrypt((unsigned char *)ciphertext.data(), &outLen, (unsigned char *)passwordBytes.data(), passwordBytes.size(), nullptr, 0, nullptr,
106 (unsigned char *)nonce.data(), (unsigned char *)databaseKey.data());
107
108 QSqlQuery query;
109 // Check if it exists
110 query.prepare("SELECT id FROM passwords WHERE origin = ? AND username = ?");
111 query.addBindValue(origin);
112 query.addBindValue(username);
113
114 if (query.exec() && query.next()) {
115 int id = query.value(0).toInt();
116 query.prepare(
117 "UPDATE passwords SET password_ciphertext = ?, "
118 "password_nonce = ?, updated_at = ? WHERE id = ?");
119 query.addBindValue(ciphertext);
120 query.addBindValue(nonce);
121 query.addBindValue(QDateTime::currentDateTime());
122 query.addBindValue(id);
123 } else {
124 query.prepare(
125 "INSERT INTO passwords (origin, username, password_ciphertext, "
126 "password_nonce, created_at, updated_at) "
127 "VALUES (?, ?, ?, ?, ?, ?)");
128 query.addBindValue(origin);
129 query.addBindValue(username);
130 query.addBindValue(ciphertext);
131 query.addBindValue(nonce);
132 query.addBindValue(QDateTime::currentDateTime());
133 query.addBindValue(QDateTime::currentDateTime());
134 }
135
136 return query.exec();
137}
138
139QList<PasswordEntry> VaultManager::getPasswords(const QString &origin) {
140 QList<PasswordEntry> entries;
141 if (!isUnlocked()) {
142 return entries;
143 }
144
145 QSqlQuery query;
146 if (origin.isEmpty()) {
147 query.prepare(
148 "SELECT id, origin, username, password_ciphertext, "
149 "password_nonce, created_at, updated_at FROM passwords");
150 } else {
151 query.prepare(
152 "SELECT id, origin, username, password_ciphertext, password_nonce, "
153 "created_at, updated_at FROM passwords WHERE origin = ?");
154 query.addBindValue(origin);
155 }
156
157 if (!query.exec()) {
158 return entries;
159 }
160
161 while (query.next()) {
162 QByteArray ciphertext = query.value(3).toByteArray();
163 QByteArray nonce = query.value(4).toByteArray();
164 QByteArray decrypted(ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES, 0);
165 unsigned long long outLen;
166
167 if (crypto_aead_xchacha20poly1305_ietf_decrypt((unsigned char *)decrypted.data(), &outLen, nullptr, (unsigned char *)ciphertext.data(), ciphertext.size(), nullptr, 0,
168 (unsigned char *)nonce.data(), (unsigned char *)databaseKey.data()) == 0) {
169 PasswordEntry entry;
170 entry.id = query.value(0).toInt();
171 entry.origin = query.value(1).toString();
172 entry.username = query.value(2).toString();
173 entry.password = QString::fromUtf8(decrypted);
174 entry.created_at = query.value(5).toDateTime();
175 entry.updated_at = query.value(6).toDateTime();
176 entries.append(entry);
177 }
178 }
179
180 return entries;
181}