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}