Skip to content
API Documentation
BastionGuard™
PasswordVault Class Reference

#include <PasswordVault.hpp>

Public Member Functions

 PasswordVault ()
bool initialize ()
bool isUnlocked () const
bool isFirstRun () const
bool createMasterPassword (const std::string &masterPassword)
bool unlock (const std::string &masterPassword)
void lock ()
std::vector< PasswordEntry > loadIndex () const
bool saveIndex (const std::vector< PasswordEntry > &entries) const
bool addEntry (const PasswordEntry &entry, const std::string &secret)
bool updateEntry (const PasswordEntry &entry, const std::string &secret)
bool deleteEntry (const std::string &id)
void deleteAllEntries (std::function< void(bool)> onDone)
void addEntriesBulk (const std::vector< std::pair< PasswordEntry, std::string > > &items, std::function< void(int ok, int fail)> onDone)
void exportToCSV (std::function< void(std::string csv, std::string err)> onDone) const
void getSecretAsync (const std::string &id, std::function< void(std::string)> callback) const
bool setSecret (const std::string &id, const std::string &secret)
bool isCacheReady () const
std::string generateId () const
std::string nowIso () const
std::string getIndexPath () const

Private Member Functions

std::string getHashPath () const
std::string getSecretsPath () const
std::string pbkdf2Hex (const std::string &password, const std::string &saltHex, int iterations=200000) const
std::string generateSaltHex () const
std::string generateNonceHex () const
bool saveSecretsFile (const std::unordered_map< std::string, std::string > &secrets) const
bool loadSecretsFile (std::unordered_map< std::string, std::string > &secrets) const
bool aesEncrypt (const std::string &key32, const std::string &nonce12, const std::string &plaintext, std::string &ciphertext, std::string &tag16) const
bool aesDecrypt (const std::string &key32, const std::string &nonce12, const std::string &ciphertext, const std::string &tag16, std::string &plaintext) const
std::string deriveKey (const std::string &saltHex) const
void flushAsync (std::unordered_map< std::string, std::string > snapshot) const
std::string makeSecretKey (const std::string &id) const

Static Private Member Functions

static bool atomicWriteFile (const std::string &path, const std::string &content)

Private Attributes

std::atomic< bool > unlocked_ { false }
std::string masterPassword_
std::unordered_map< std::string, std::string > secretCache_
std::mutex cacheMutex_
std::atomic< bool > cacheReady_ { false }

Detailed Description

Definition at line 32 of file PasswordVault.hpp.

Constructor & Destructor Documentation

◆ PasswordVault()

PasswordVault::PasswordVault ( )

Definition at line 263 of file PasswordVault.cpp.

263: unlocked_(false) {}
std::atomic< bool > unlocked_

References unlocked_.

Member Function Documentation

◆ addEntriesBulk()

void PasswordVault::addEntriesBulk ( const std::vector< std::pair< PasswordEntry, std::string > > & items,
std::function< void(int ok, int fail)> onDone )

Definition at line 473 of file PasswordVault.cpp.

476{
477 if (!unlocked_) { if (onDone) onDone(0, (int)items.size()); return; }
478
479 auto entries = loadIndex();
480 std::unordered_set<std::string> existingIds;
481 existingIds.reserve(entries.size());
482 for (const auto& e : entries) existingIds.insert(e.id);
483
484 int ok = 0, fail = 0;
485 for (const auto& [entry, secret] : items) {
486 if (entry.id.empty() || entry.name.empty() || existingIds.count(entry.id)) {
487 ++fail; continue;
488 }
489 entries.push_back(entry);
490 existingIds.insert(entry.id);
491 ++ok;
492 }
493
494 if (!saveIndex(entries)) { if (onDone) onDone(0, (int)items.size()); return; }
495 {
496 std::lock_guard<std::mutex> lk(cacheMutex_);
497 for (const auto& [entry, secret] : items)
498 if (!entry.id.empty()) secretCache_[entry.id] = secret;
499 }
500 if (onDone) onDone(ok, fail);
501 std::unordered_map<std::string,std::string> snap;
502 { std::lock_guard<std::mutex> lk(cacheMutex_); snap = secretCache_; }
503 flushAsync(std::move(snap));
504}
std::unordered_map< std::string, std::string > secretCache_
std::mutex cacheMutex_
void flushAsync(std::unordered_map< std::string, std::string > snapshot) const
std::vector< PasswordEntry > loadIndex() const
bool saveIndex(const std::vector< PasswordEntry > &entries) const
void fail(const std::string &message)

References cacheMutex_, flushAsync(), loadIndex(), saveIndex(), secretCache_, and unlocked_.

Referenced by PasswordManagerPage::onImportClicked().

◆ addEntry()

bool PasswordVault::addEntry ( const PasswordEntry & entry,
const std::string & secret )

Definition at line 396 of file PasswordVault.cpp.

396 {
397 if (!unlocked_ || entry.id.empty() || entry.name.empty()) return false;
398 auto entries = loadIndex();
399 for (const auto& e : entries)
400 if (e.id == entry.id) return false;
401 entries.push_back(entry);
402 if (!saveIndex(entries)) return false;
403
404 std::unordered_map<std::string,std::string> snap;
405 {
406 std::lock_guard<std::mutex> lk(cacheMutex_);
407 secretCache_[entry.id] = secret;
408 snap = secretCache_;
409 }
410 flushAsync(std::move(snap));
411 return true;
412}
std::string id
std::string name

References cacheMutex_, flushAsync(), PasswordEntry::id, loadIndex(), PasswordEntry::name, saveIndex(), secretCache_, and unlocked_.

Referenced by PasswordManagerPage::onAddClicked().

◆ aesDecrypt()

bool PasswordVault::aesDecrypt ( const std::string & key32,
const std::string & nonce12,
const std::string & ciphertext,
const std::string & tag16,
std::string & plaintext ) const
private

Definition at line 134 of file PasswordVault.cpp.

139{
140 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
141 if (!ctx) return false;
142
143 plaintext.resize(ciphertext.size());
144 int len = 0;
145 bool ok = true;
146
147 ok = ok && EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) == 1;
148 ok = ok && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr) == 1;
149 ok = ok && EVP_DecryptInit_ex(ctx, nullptr, nullptr,
150 reinterpret_cast<const unsigned char*>(key32.data()),
151 reinterpret_cast<const unsigned char*>(nonce12.data())) == 1;
152 if (!ciphertext.empty())
153 ok = ok && EVP_DecryptUpdate(ctx,
154 reinterpret_cast<unsigned char*>(plaintext.data()),
155 &len,
156 reinterpret_cast<const unsigned char*>(ciphertext.data()),
157 (int)ciphertext.size()) == 1;
158 ok = ok && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,
159 const_cast<void*>(static_cast<const void*>(tag16.data()))) == 1;
160 int finalLen = 0;
161 bool authOk = ok && EVP_DecryptFinal_ex(ctx,
162 reinterpret_cast<unsigned char*>(plaintext.data()) + len,
163 &finalLen) > 0;
164 EVP_CIPHER_CTX_free(ctx);
165 if (!authOk) { plaintext.clear(); return false; }
166 plaintext.resize(len + finalLen);
167 return true;
168}

Referenced by loadSecretsFile().

◆ aesEncrypt()

bool PasswordVault::aesEncrypt ( const std::string & key32,
const std::string & nonce12,
const std::string & plaintext,
std::string & ciphertext,
std::string & tag16 ) const
private

Definition at line 97 of file PasswordVault.cpp.

102{
103 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
104 if (!ctx) return false;
105
106 ciphertext.resize(plaintext.size());
107 tag16.resize(16);
108
109 int len = 0;
110 bool ok = true;
111
112 ok = ok && EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) == 1;
113 ok = ok && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr) == 1;
114 ok = ok && EVP_EncryptInit_ex(ctx, nullptr, nullptr,
115 reinterpret_cast<const unsigned char*>(key32.data()),
116 reinterpret_cast<const unsigned char*>(nonce12.data())) == 1;
117 ok = ok && EVP_EncryptUpdate(ctx, nullptr, &len, nullptr, 0) == 1; // no AAD
118 if (!plaintext.empty())
119 ok = ok && EVP_EncryptUpdate(ctx,
120 reinterpret_cast<unsigned char*>(ciphertext.data()),
121 &len,
122 reinterpret_cast<const unsigned char*>(plaintext.data()),
123 (int)plaintext.size()) == 1;
124 int finalLen = 0;
125 ok = ok && EVP_EncryptFinal_ex(ctx,
126 reinterpret_cast<unsigned char*>(ciphertext.data()) + len,
127 &finalLen) == 1;
128 ok = ok && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16,
129 reinterpret_cast<void*>(tag16.data())) == 1;
130 EVP_CIPHER_CTX_free(ctx);
131 return ok;
132}

Referenced by saveSecretsFile().

◆ atomicWriteFile()

bool PasswordVault::atomicWriteFile ( const std::string & path,
const std::string & content )
staticprivate

Definition at line 85 of file PasswordVault.cpp.

85 {
86 const std::string tmp = path + ".tmp";
87 {
88 std::ofstream out(tmp, std::ios::trunc | std::ios::binary);
89 if (!out.good()) return false;
90 out << content;
91 if (!out.good()) return false;
92 }
93 return std::rename(tmp.c_str(), path.c_str()) == 0;
94}

Referenced by createMasterPassword(), deleteAllEntries(), initialize(), saveIndex(), and saveSecretsFile().

◆ createMasterPassword()

bool PasswordVault::createMasterPassword ( const std::string & masterPassword)

Definition at line 286 of file PasswordVault.cpp.

286 {
287 if (masterPassword.size() < 8) return false;
288 const std::string saltHex = generateSaltHex();
289 const std::string hashHex = pbkdf2Hex(masterPassword, saltHex);
290 json j = { {"salt", saltHex}, {"hash", hashHex}, {"iterations", 200000} };
291 if (!atomicWriteFile(getHashPath(), j.dump())) return false;
292 masterPassword_ = masterPassword;
293 unlocked_ = true;
294 cacheReady_ = true; // nessun secret da caricare al primo avvio
295 return true;
296}
nlohmann::json json
Definition Backend.cpp:2908
std::string masterPassword_
std::atomic< bool > cacheReady_
std::string pbkdf2Hex(const std::string &password, const std::string &saltHex, int iterations=200000) const
std::string getHashPath() const
std::string generateSaltHex() const
static bool atomicWriteFile(const std::string &path, const std::string &content)

References atomicWriteFile(), cacheReady_, generateSaltHex(), getHashPath(), masterPassword_, pbkdf2Hex(), and unlocked_.

Referenced by PasswordManagerPage::onUnlockClicked().

◆ deleteAllEntries()

void PasswordVault::deleteAllEntries ( std::function< void(bool)> onDone)

Definition at line 455 of file PasswordVault.cpp.

455 {
456 if (!unlocked_) { if (onDone) onDone(false); return; }
457 if (!atomicWriteFile(getIndexPath(), "[]")) { if (onDone) onDone(false); return; }
458 {
459 std::lock_guard<std::mutex> lk(cacheMutex_);
460 secretCache_.clear();
461 }
462 auto disp = std::make_shared<Glib::Dispatcher>();
463 auto result = std::make_shared<bool>(false);
464 auto cb = std::make_shared<std::function<void(bool)>>(std::move(onDone));
465 disp->connect([disp, result, cb]() { if (*cb) (*cb)(*result); });
466 const std::string path = getSecretsPath();
467 std::thread([disp, result, path]() {
468 *result = (std::remove(path.c_str()) == 0 || errno == ENOENT);
469 disp->emit();
470 }).detach();
471}
std::string getSecretsPath() const
std::string getIndexPath() const

References atomicWriteFile(), cacheMutex_, getIndexPath(), getSecretsPath(), secretCache_, and unlocked_.

Referenced by PasswordManagerPage::onDeleteAllClicked().

◆ deleteEntry()

bool PasswordVault::deleteEntry ( const std::string & id)

Definition at line 434 of file PasswordVault.cpp.

434 {
435 if (!unlocked_ || id.empty()) return false;
436 auto entries = loadIndex();
437 const auto before = entries.size();
438 std::vector<PasswordEntry> filtered;
439 filtered.reserve(before);
440 for (const auto& e : entries)
441 if (e.id != id) filtered.push_back(e);
442 if (filtered.size() == before) return false;
443 if (!saveIndex(filtered)) return false;
444
445 std::unordered_map<std::string,std::string> snap;
446 {
447 std::lock_guard<std::mutex> lk(cacheMutex_);
448 secretCache_.erase(id);
449 snap = secretCache_;
450 }
451 flushAsync(std::move(snap));
452 return true;
453}

References cacheMutex_, flushAsync(), loadIndex(), saveIndex(), secretCache_, and unlocked_.

Referenced by PasswordManagerPage::onDeleteEntry().

◆ deriveKey()

std::string PasswordVault::deriveKey ( const std::string & saltHex) const
private

Definition at line 170 of file PasswordVault.cpp.

170 {
171 const std::string salt = fromHex(saltHex);
172 unsigned char key[32];
173 PKCS5_PBKDF2_HMAC(masterPassword_.c_str(), (int)masterPassword_.size(),
174 reinterpret_cast<const unsigned char*>(salt.data()), (int)salt.size(),
175 100000, EVP_sha256(), 32, key);
176 return std::string(reinterpret_cast<char*>(key), 32);
177}
static std::string fromHex(const std::string &hex)

References fromHex(), and masterPassword_.

Referenced by loadSecretsFile(), and saveSecretsFile().

◆ exportToCSV()

void PasswordVault::exportToCSV ( std::function< void(std::string csv, std::string err)> onDone) const

Definition at line 507 of file PasswordVault.cpp.

509{
510 if (!unlocked_) { if (onDone) onDone("", "Vault bloccato."); return; }
511 const auto entries = loadIndex();
512 auto resultCsv = std::make_shared<std::string>();
513 auto disp = std::make_shared<Glib::Dispatcher>();
514 auto cb = std::make_shared<std::function<void(std::string,std::string)>>(onDone);
515 disp->connect([disp, resultCsv, cb]() { if (*cb) (*cb)(std::move(*resultCsv), ""); });
516
517 std::unordered_map<std::string,std::string> snap;
518 { std::lock_guard<std::mutex> lk(cacheMutex_); snap = secretCache_; }
519
520 std::thread([entries, snap, resultCsv, disp]() {
521 std::ostringstream csv;
522 csv << "name,url,username,password,notes\n";
523 for (const auto& e : entries) {
524 std::string pwd;
525 auto it = snap.find(e.id);
526 if (it != snap.end()) pwd = it->second;
527 csv << csvEscape(e.name) << ","
528 << csvEscape(e.url) << ","
529 << csvEscape(e.username) << ","
530 << csvEscape(pwd) << ","
531 << csvEscape(e.notes) << "\n";
532 }
533 *resultCsv = csv.str();
534 disp->emit();
535 }).detach();
536}
static std::string csvEscape(const std::string &s)

References cacheMutex_, csvEscape(), loadIndex(), secretCache_, and unlocked_.

Referenced by PasswordVaultView::onExportClicked().

◆ flushAsync()

void PasswordVault::flushAsync ( std::unordered_map< std::string, std::string > snapshot) const
private

Definition at line 389 of file PasswordVault.cpp.

390 {
391 std::thread([this, snapshot]() {
392 this->saveSecretsFile(snapshot);
393 }).detach();
394}
bool saveSecretsFile(const std::unordered_map< std::string, std::string > &secrets) const

References saveSecretsFile().

Referenced by addEntriesBulk(), addEntry(), deleteEntry(), setSecret(), and updateEntry().

◆ generateId()

std::string PasswordVault::generateId ( ) const

Definition at line 588 of file PasswordVault.cpp.

588 {
589 std::random_device rd;
590 std::mt19937_64 gen(rd());
591 std::uniform_int_distribution<uint32_t> dist32;
592 uint32_t data[4] = { dist32(gen), dist32(gen), dist32(gen), dist32(gen) };
593 data[1] = (data[1] & 0xFFFF0FFF) | 0x00004000;
594 data[2] = (data[2] & 0x3FFFFFFF) | 0x80000000;
595 std::ostringstream oss;
596 oss << std::hex << std::setfill('0');
597 oss << std::setw(8) << data[0] << "-"
598 << std::setw(4) << (data[1] >> 16) << "-"
599 << std::setw(4) << (data[1] & 0xFFFF) << "-"
600 << std::setw(4) << (data[2] >> 16) << "-"
601 << std::setw(4) << (data[2] & 0xFFFF)
602 << std::setw(8) << data[3];
603 return oss.str();
604}

Referenced by PasswordManagerPage::onAddClicked(), and PasswordManagerPage::onImportClicked().

◆ generateNonceHex()

std::string PasswordVault::generateNonceHex ( ) const
private

Definition at line 196 of file PasswordVault.cpp.

196 {
197 unsigned char nonce[12];
198 RAND_bytes(nonce, sizeof(nonce));
199 return toHex(nonce, sizeof(nonce));
200}
static std::string toHex(const unsigned char *data, size_t len)

References toHex().

Referenced by saveSecretsFile().

◆ generateSaltHex()

std::string PasswordVault::generateSaltHex ( ) const
private

Definition at line 190 of file PasswordVault.cpp.

190 {
191 unsigned char salt[16];
192 RAND_bytes(salt, sizeof(salt));
193 return toHex(salt, sizeof(salt));
194}

References toHex().

Referenced by createMasterPassword(), and saveSecretsFile().

◆ getHashPath()

std::string PasswordVault::getHashPath ( ) const
private

Definition at line 343 of file PasswordVault.cpp.

343 {
344 return Glib::get_home_dir() + "/.config/BastionGuard/password_manager/master.json";
345}

Referenced by createMasterPassword(), isFirstRun(), and unlock().

◆ getIndexPath()

std::string PasswordVault::getIndexPath ( ) const

Definition at line 340 of file PasswordVault.cpp.

340 {
341 return Glib::get_home_dir() + "/.config/BastionGuard/password_manager/index.json";
342}

Referenced by deleteAllEntries(), initialize(), loadIndex(), and saveIndex().

◆ getSecretAsync()

void PasswordVault::getSecretAsync ( const std::string & id,
std::function< void(std::string)> callback ) const

Definition at line 539 of file PasswordVault.cpp.

541{
542 if (!unlocked_ || id.empty()) { if (callback) callback({}); return; }
543
544 {
545 std::lock_guard<std::mutex> lk(cacheMutex_);
546 auto it = secretCache_.find(id);
547 if (it != secretCache_.end()) {
548 const std::string cached = it->second;
549 Glib::signal_idle().connect_once([callback, cached]() {
550 if (callback) callback(cached);
551 });
552 return;
553 }
554 }
555
556 auto retries = std::make_shared<int>(0);
557 Glib::signal_timeout().connect(
558 [this, id, callback, retries]() -> bool {
559 ++(*retries);
560 {
561 std::lock_guard<std::mutex> lk(cacheMutex_);
562 auto it = secretCache_.find(id);
563 if (it != secretCache_.end()) {
564 if (callback) callback(it->second);
565 return false;
566 }
567 }
568 if (cacheReady_.load() || *retries > 100) {
569 if (callback) callback({});
570 return false;
571 }
572 return true;
573 }, 50);
574}

References cacheMutex_, cacheReady_, secretCache_, and unlocked_.

Referenced by PasswordManagerPage::onEditEntry(), PasswordManagerPage::onRevealEntry(), and PasswordVaultView::showEntryDetail().

◆ getSecretsPath()

std::string PasswordVault::getSecretsPath ( ) const
private

Definition at line 346 of file PasswordVault.cpp.

346 {
347 return Glib::get_home_dir() + "/.config/BastionGuard/password_manager/secrets.enc";
348}

Referenced by deleteAllEntries(), loadSecretsFile(), and saveSecretsFile().

◆ initialize()

bool PasswordVault::initialize ( )

Definition at line 265 of file PasswordVault.cpp.

265 {
266 const auto dir = Glib::get_home_dir() + "/.config/BastionGuard/password_manager";
267 auto dirFile = Gio::File::create_for_path(dir);
268 try {
269 if (!dirFile->query_exists())
270 dirFile->make_directory_with_parents();
271 } catch (...) { return false; }
272 const auto indexPath = getIndexPath();
273 std::ifstream in(indexPath);
274 if (!in.good())
275 if (!atomicWriteFile(indexPath, "[]")) return false;
276 return true;
277}

References atomicWriteFile(), and getIndexPath().

Referenced by PasswordManagerPage::PasswordManagerPage().

◆ isCacheReady()

bool PasswordVault::isCacheReady ( ) const
inline

Definition at line 61 of file PasswordVault.hpp.

61{ return cacheReady_.load(); }

References cacheReady_.

◆ isFirstRun()

bool PasswordVault::isFirstRun ( ) const

Definition at line 281 of file PasswordVault.cpp.

281 {
282 std::ifstream f(getHashPath());
283 return !f.good();
284}

References getHashPath().

Referenced by PasswordManagerPage::onUnlockClicked().

◆ isUnlocked()

◆ loadIndex()

std::vector< PasswordEntry > PasswordVault::loadIndex ( ) const

Definition at line 353 of file PasswordVault.cpp.

353 {
354 std::vector<PasswordEntry> entries;
355 std::ifstream in(getIndexPath());
356 if (!in.good()) return entries;
357 json j;
358 try { in >> j; } catch (...) { return entries; }
359 if (!j.is_array()) return entries;
360 for (const auto& item : j) {
361 if (!item.is_object()) continue;
362 PasswordEntry e;
363 e.id = item.value("id", "");
364 e.name = item.value("name", "");
365 e.username = item.value("username", "");
366 e.url = item.value("url", "");
367 e.notes = item.value("notes", "");
368 e.created_at = item.value("created_at", "");
369 e.updated_at = item.value("updated_at", "");
370 if (e.id.empty()) continue;
371 if (item.contains("tags") && item["tags"].is_array())
372 for (const auto& tag : item["tags"])
373 if (tag.is_string()) e.tags.push_back(tag.get<std::string>());
374 entries.push_back(std::move(e));
375 }
376 return entries;
377}
std::string created_at
std::string url
std::string username
std::string updated_at
std::vector< std::string > tags
std::string notes

References PasswordEntry::created_at, getIndexPath(), PasswordEntry::id, PasswordEntry::name, PasswordEntry::notes, PasswordEntry::tags, PasswordEntry::updated_at, PasswordEntry::url, and PasswordEntry::username.

Referenced by addEntriesBulk(), addEntry(), deleteEntry(), exportToCSV(), PasswordVaultView::present(), PasswordManagerPage::reload(), and updateEntry().

◆ loadSecretsFile()

bool PasswordVault::loadSecretsFile ( std::unordered_map< std::string, std::string > & secrets) const
private

Definition at line 227 of file PasswordVault.cpp.

229{
230 secrets.clear();
231 std::ifstream in(getSecretsPath());
232 if (!in.good()) return true;
233
234 json fileJ;
235 try { in >> fileJ; } catch (...) { return false; }
236
237 const std::string saltHex = fileJ.value("salt", "");
238 const std::string nonceHex = fileJ.value("nonce", "");
239 const std::string ctHex = fileJ.value("ct", "");
240 const std::string tagHex = fileJ.value("tag", "");
241 if (saltHex.empty() || nonceHex.empty()) return false;
242
243 const std::string key32 = deriveKey(saltHex);
244 const std::string nonce12 = fromHex(nonceHex);
245 const std::string ct = fromHex(ctHex);
246 const std::string tag = fromHex(tagHex);
247
248 std::string plaintext;
249 if (!aesDecrypt(key32, nonce12, ct, tag, plaintext)) return false;
250
251 json plainJ;
252 try { plainJ = json::parse(plaintext); } catch (...) { return false; }
253 if (!plainJ.is_object()) return false;
254
255 for (auto it = plainJ.begin(); it != plainJ.end(); ++it)
256 if (it.value().is_string())
257 secrets[it.key()] = it.value().get<std::string>();
258
259 return true;
260}
bool aesDecrypt(const std::string &key32, const std::string &nonce12, const std::string &ciphertext, const std::string &tag16, std::string &plaintext) const
std::string deriveKey(const std::string &saltHex) const

References aesDecrypt(), deriveKey(), fromHex(), and getSecretsPath().

Referenced by unlock().

◆ lock()

void PasswordVault::lock ( )

Definition at line 332 of file PasswordVault.cpp.

332 {
334 unlocked_ = false;
335 cacheReady_ = false;
336 std::lock_guard<std::mutex> lk(cacheMutex_);
337 secretCache_.clear();
338}
static void secureClear(std::string &s)

References cacheMutex_, cacheReady_, masterPassword_, secretCache_, secureClear(), and unlocked_.

Referenced by PasswordManagerPage::onLockClicked().

◆ makeSecretKey()

std::string PasswordVault::makeSecretKey ( const std::string & id) const
private

Definition at line 349 of file PasswordVault.cpp.

349 {
350 return id;
351}

◆ nowIso()

std::string PasswordVault::nowIso ( ) const

Definition at line 606 of file PasswordVault.cpp.

606 {
607 std::time_t t = std::time(nullptr);
608 std::tm tm{};
609 localtime_r(&t, &tm);
610 std::ostringstream oss;
611 oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
612 return oss.str();
613}

Referenced by PasswordManagerPage::onAddClicked(), and PasswordManagerPage::onImportClicked().

◆ pbkdf2Hex()

std::string PasswordVault::pbkdf2Hex ( const std::string & password,
const std::string & saltHex,
int iterations = 200000 ) const
private

Definition at line 179 of file PasswordVault.cpp.

181 {
182 const std::string salt = fromHex(saltHex);
183 unsigned char out[32];
184 PKCS5_PBKDF2_HMAC(password.c_str(), (int)password.size(),
185 reinterpret_cast<const unsigned char*>(salt.data()), (int)salt.size(),
186 iterations, EVP_sha256(), 32, out);
187 return toHex(out, 32);
188}

References fromHex(), and toHex().

Referenced by createMasterPassword(), and unlock().

◆ saveIndex()

bool PasswordVault::saveIndex ( const std::vector< PasswordEntry > & entries) const

Definition at line 379 of file PasswordVault.cpp.

379 {
380 json j = json::array();
381 for (const auto& e : entries)
382 j.push_back({{"id",e.id},{"name",e.name},{"username",e.username},
383 {"url",e.url},{"notes",e.notes},{"tags",e.tags},
384 {"created_at",e.created_at},{"updated_at",e.updated_at}});
385 return atomicWriteFile(getIndexPath(), j.dump(4));
386}

References atomicWriteFile(), and getIndexPath().

Referenced by addEntriesBulk(), addEntry(), deleteEntry(), and updateEntry().

◆ saveSecretsFile()

bool PasswordVault::saveSecretsFile ( const std::unordered_map< std::string, std::string > & secrets) const
private

Definition at line 203 of file PasswordVault.cpp.

205{
206 json plainJ = json::object();
207 for (const auto& [id, pwd] : secrets)
208 plainJ[id] = pwd;
209 const std::string plaintext = plainJ.dump();
210 const std::string saltHex = generateSaltHex();
211 const std::string nonceHex = generateNonceHex();
212 const std::string key32 = deriveKey(saltHex);
213 const std::string nonce12 = fromHex(nonceHex);
214
215 std::string ct, tag;
216 if (!aesEncrypt(key32, nonce12, plaintext, ct, tag)) return false;
217
218 json fileJ = {
219 {"salt", saltHex},
220 {"nonce", nonceHex},
221 {"ct", toHex(ct)},
222 {"tag", toHex(tag)}
223 };
224 return atomicWriteFile(getSecretsPath(), fileJ.dump());
225}
std::string generateNonceHex() const
bool aesEncrypt(const std::string &key32, const std::string &nonce12, const std::string &plaintext, std::string &ciphertext, std::string &tag16) const

References aesEncrypt(), atomicWriteFile(), deriveKey(), fromHex(), generateNonceHex(), generateSaltHex(), getSecretsPath(), and toHex().

Referenced by flushAsync().

◆ setSecret()

bool PasswordVault::setSecret ( const std::string & id,
const std::string & secret )

Definition at line 576 of file PasswordVault.cpp.

576 {
577 if (id.empty()) return false;
578 std::unordered_map<std::string,std::string> snap;
579 {
580 std::lock_guard<std::mutex> lk(cacheMutex_);
581 secretCache_[id] = secret;
582 snap = secretCache_;
583 }
584 flushAsync(std::move(snap));
585 return true;
586}

References cacheMutex_, flushAsync(), and secretCache_.

◆ unlock()

bool PasswordVault::unlock ( const std::string & masterPassword)

Definition at line 298 of file PasswordVault.cpp.

298 {
299 if (masterPassword.size() < 8) return false;
300 std::ifstream f(getHashPath());
301 if (!f.good()) return false;
302 json j;
303 try { f >> j; } catch (...) { return false; }
304 const std::string saltHex = j.value("salt", "");
305 const std::string savedHash = j.value("hash", "");
306 const int iterations = j.value("iterations", 200000);
307 if (saltHex.empty() || savedHash.empty()) return false;
308 const std::string derived = pbkdf2Hex(masterPassword, saltHex, iterations);
309 if (derived.size() != savedHash.size()) return false;
310 unsigned char diff = 0;
311 for (size_t i = 0; i < derived.size(); ++i)
312 diff |= static_cast<unsigned char>(derived[i]) ^
313 static_cast<unsigned char>(savedHash[i]);
314 if (diff != 0) return false;
315
316 masterPassword_ = masterPassword;
317 unlocked_ = true;
318 cacheReady_ = false;
319 std::thread([this]() {
320 std::unordered_map<std::string, std::string> loaded;
321 const bool ok = loadSecretsFile(loaded);
322 if (unlocked_) {
323 std::lock_guard<std::mutex> lk(cacheMutex_);
324 if (ok) secretCache_ = std::move(loaded);
325 }
326 cacheReady_ = true;
327 }).detach();
328
329 return true;
330}
bool loadSecretsFile(std::unordered_map< std::string, std::string > &secrets) const

References cacheMutex_, cacheReady_, getHashPath(), loadSecretsFile(), masterPassword_, pbkdf2Hex(), secretCache_, and unlocked_.

Referenced by PasswordManagerPage::onUnlockClicked().

◆ updateEntry()

bool PasswordVault::updateEntry ( const PasswordEntry & entry,
const std::string & secret )

Definition at line 414 of file PasswordVault.cpp.

414 {
415 if (!unlocked_ || entry.id.empty()) return false;
416 auto entries = loadIndex();
417 for (auto& e : entries) {
418 if (e.id == entry.id) {
419 e = entry;
420 if (!saveIndex(entries)) return false;
421 std::unordered_map<std::string,std::string> snap;
422 {
423 std::lock_guard<std::mutex> lk(cacheMutex_);
424 secretCache_[entry.id] = secret;
425 snap = secretCache_;
426 }
427 flushAsync(std::move(snap));
428 return true;
429 }
430 }
431 return false;
432}

References cacheMutex_, flushAsync(), PasswordEntry::id, loadIndex(), saveIndex(), secretCache_, and unlocked_.

Member Data Documentation

◆ cacheMutex_

std::mutex PasswordVault::cacheMutex_
mutableprivate

◆ cacheReady_

std::atomic<bool> PasswordVault::cacheReady_ { false }
mutableprivate

Definition at line 75 of file PasswordVault.hpp.

Referenced by createMasterPassword(), getSecretAsync(), isCacheReady(), lock(), and unlock().

◆ masterPassword_

std::string PasswordVault::masterPassword_
private

Definition at line 72 of file PasswordVault.hpp.

Referenced by createMasterPassword(), deriveKey(), lock(), and unlock().

◆ secretCache_

std::unordered_map<std::string, std::string> PasswordVault::secretCache_
mutableprivate

◆ unlocked_

std::atomic<bool> PasswordVault::unlocked_ { false }
private

The documentation for this class was generated from the following files: