Passwordfile library 5.2.0
C++ library to read/write passwords from/to encrypted files
Loading...
Searching...
No Matches
passwordfile.cpp
Go to the documentation of this file.
1#include "./passwordfile.h"
2#include "./cryptoexception.h"
3#include "./entry.h"
5
6#include "../util/openssl.h"
7
8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/conversion/stringconversion.h>
10#include <c++utilities/io/path.h>
11
12#include <openssl/conf.h>
13#include <openssl/crypto.h>
14#include <openssl/err.h>
15#include <openssl/evp.h>
16#include <openssl/rand.h>
17
18#include <zlib.h>
19
20#include <cstring>
21#include <filesystem>
22#include <fstream>
23#include <functional>
24#include <iostream>
25#include <limits>
26#include <memory>
27#include <sstream>
28#include <type_traits>
29
30using namespace std;
31using namespace CppUtilities;
32
33namespace Io {
34
35constexpr auto aes256cbcIvSize = 16U;
36constexpr auto aes256blockSize = 32U;
38
39enum class ExtendedHeaderFieldIds : std::uint32_t {
40 AuthTag = 0x686D6163U,
41};
42
43constexpr auto authTagSize = Util::OpenSsl::Sha256Sum::size; // HMAC-SHA256
44constexpr auto extendedHeaderFieldIdSize = sizeof(std::underlying_type<ExtendedHeaderFieldIds>::type);
46
52
57 : m_freader(BinaryReader(&m_file))
58 , m_fwriter(BinaryWriter(&m_file))
59 , m_version(0)
60 , m_openOptions(PasswordFileOpenFlags::None)
61 , m_saveOptions(PasswordFileSaveFlags::None)
62{
63 m_file.exceptions(ios_base::failbit | ios_base::badbit);
65}
66
70PasswordFile::PasswordFile(const string &path, const string &password)
71 : m_freader(BinaryReader(&m_file))
72 , m_fwriter(BinaryWriter(&m_file))
73 , m_version(0)
74 , m_openOptions(PasswordFileOpenFlags::None)
75 , m_saveOptions(PasswordFileSaveFlags::None)
76{
77 m_file.exceptions(ios_base::failbit | ios_base::badbit);
80}
81
86 : m_path(other.m_path)
87 , m_password(other.m_password)
88 , m_rootEntry(other.m_rootEntry ? make_unique<NodeEntry>(*other.m_rootEntry) : nullptr)
89 , m_extendedHeader(other.m_extendedHeader)
90 , m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
91 , m_freader(BinaryReader(&m_file))
92 , m_fwriter(BinaryWriter(&m_file))
93 , m_version(other.m_version)
94 , m_openOptions(other.m_openOptions)
95 , m_saveOptions(other.m_saveOptions)
96{
97 m_file.exceptions(ios_base::failbit | ios_base::badbit);
98}
99
104 : m_path(std::move(other.m_path))
105 , m_password(std::move(other.m_password))
106 , m_rootEntry(std::move(other.m_rootEntry))
107 , m_extendedHeader(std::move(other.m_extendedHeader))
108 , m_encryptedExtendedHeader(std::move(other.m_encryptedExtendedHeader))
109 , m_file(std::move(other.m_file))
110 , m_freader(BinaryReader(&m_file))
111 , m_fwriter(BinaryWriter(&m_file))
112 , m_version(other.m_version)
113 , m_openOptions(other.m_openOptions)
114 , m_saveOptions(other.m_saveOptions)
115{
116}
117
124
130{
131 if (options & PasswordFileOpenFlags::New) {
132 return create();
133 }
134 close();
135 if (m_path.empty()) {
136 throw std::ios_base::failure("Unable to open file because path is empty.");
137 }
138 m_file.open(
139 m_path, options & PasswordFileOpenFlags::ReadOnly ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary);
140 m_openOptions = options;
141 opened();
142}
143
150{
151 m_file.seekg(0, ios_base::end);
152 if (m_file.tellg() == 0) {
153 throw std::ios_base::failure("File is empty.");
154 } else {
155 m_file.seekg(0);
156 }
157}
158
163{
164 if (!m_rootEntry) {
165 m_rootEntry.reset(new NodeEntry("accounts"));
166 }
167}
168
174{
175 close();
176 if (m_path.empty()) {
177 throw std::ios_base::failure("Unable to create file because path is empty.");
178 }
179 m_file.open(m_path, fstream::out | fstream::trunc | fstream::binary);
180 m_openOptions += PasswordFileOpenFlags::New;
181}
182
192{
193 if (!m_file.is_open()) {
194 open();
195 }
196 m_file.seekg(0);
197 m_version = 0;
198 m_saveOptions = PasswordFileSaveFlags::None;
199
200 // check magic number
201 if (m_freader.readUInt32LE() != 0x7770616DU) {
202 throw ParsingException("Signature not present.");
203 }
204
205 // check version and flags (used in version 0x3 only)
206 m_version = m_freader.readUInt32LE();
207 if (m_version > 0x6U) {
208 throw ParsingException(argsToString("Version \"", m_version, "\" is unknown. Only versions 0 to 6 are supported."));
209 }
210 auto decrypterUsed = false, ivUsed = false, compressionUsed = false, hmacUsed = false;
211 if (m_version >= 0x3U) {
212 const auto flags = m_freader.readByte();
213 if ((decrypterUsed = flags & 0x80)) {
214 m_saveOptions |= PasswordFileSaveFlags::Encryption;
215 }
216 if ((compressionUsed = flags & 0x20)) {
217 m_saveOptions |= PasswordFileSaveFlags::Compression;
218 }
219 if ((hmacUsed = flags & 0x10)) {
221 }
222 ivUsed = flags & 0x40;
223 } else {
224 if ((decrypterUsed = m_version >= 0x1U)) {
225 m_saveOptions |= PasswordFileSaveFlags::Encryption;
226 }
227 ivUsed = m_version == 0x2U;
228 }
229
230 // read extended header
231 auto authTag = std::vector<char>();
232 if (m_version >= 0x4U) {
233 auto extendedHeaderSize = static_cast<std::size_t>(m_freader.readUInt16BE());
234 if (extendedHeaderSize >= extendedHeaderFieldIdSize) {
235 switch (static_cast<ExtendedHeaderFieldIds>(m_freader.readUInt32BE())) {
237 // read authentication tag which will always be written as first extended field if present
238 // note: A stale auth tag might be present if `!hmacUsed`. This can happen if a file is saved with a tag and
239 // then re-saved with a version of this library that does not support tags yet. Then the tag is preserved
240 // as unknown extended data when re-saving but not updated. In this case the tag is ignored and discared
241 // when re-saving with this version of the code. The file is treated as if no tag was present at all.
242 if ((extendedHeaderSize -= extendedHeaderFieldIdSize) < authTagSize) {
243 throw ParsingException("Authentication tag is truncated.");
244 }
245 authTag.resize(authTagSize);
246 m_file.read(authTag.data(), authTagSize);
247 extendedHeaderSize -= authTagSize;
248 break;
249 default:
250 // ignore unknown field (probably written by a future version of this library)
251 m_file.seekg(-static_cast<std::streamoff>(extendedHeaderFieldIdSize));
252 break;
253 }
254 }
255 // read remaining unknown fields to preserve them
256 m_extendedHeader = m_freader.readString(extendedHeaderSize);
257 } else {
258 m_extendedHeader.clear();
259 }
260
261 // verify whether auth tag is present if flags indicate presence
262 if (hmacUsed && authTag.empty()) {
263 throw ParsingException("Authentication tag is missing.");
264 }
265
266 // get length
267 const auto headerSize = static_cast<size_t>(m_file.tellg());
268 m_file.seekg(0, ios_base::end);
269 auto remainingSize = static_cast<size_t>(m_file.tellg()) - headerSize;
270 m_file.seekg(static_cast<streamoff>(headerSize), ios_base::beg);
271
272 // read hash count (always present for version >= 6 when encrypted)
273 auto hashCount = std::uint32_t();
274 if (m_version >= 0x6U && decrypterUsed) {
275 if (remainingSize < 4) {
276 throw ParsingException("Hash count truncated.");
277 }
278 hashCount = m_freader.readUInt32BE();
279 remainingSize -= 4;
280 if (hashCount) {
282 }
283 }
284
285 // read IV
286 unsigned char iv[aes256cbcIvSize] = { 0 };
287 if (decrypterUsed && ivUsed) {
288 if (remainingSize < aes256cbcIvSize) {
289 throw ParsingException("Initiation vector is truncated.");
290 }
291 m_file.read(reinterpret_cast<char *>(iv), aes256cbcIvSize);
292 remainingSize -= aes256cbcIvSize;
293 }
294 if (!remainingSize) {
295 throw ParsingException("No contents found.");
296 }
297
298 // decrypt contents
299 auto rawData = std::vector<char>();
300 m_freader.read(rawData, static_cast<streamoff>(remainingSize));
301 auto decryptedData = std::vector<char>();
302
303 if (decrypterUsed) {
304 if (remainingSize > numeric_limits<int>::max()) {
305 throw CryptoException("Size exceeds limit.");
306 }
307
308 // prepare password
310 if (hashCount) {
311 // hash the password as often as it has been hashed when writing the file
312 password = Util::OpenSsl::computeSha256Sum(reinterpret_cast<unsigned const char *>(m_password.data()), m_password.size());
313 for (uint32_t i = 1; i < hashCount; ++i) {
315 }
316 } else {
317 m_password.copy(reinterpret_cast<char *>(password.data), Util::OpenSsl::Sha256Sum::size);
318 }
319
320 // verify HMAC-SHA256 authentication tag (version >= 7)
321 if (hmacUsed) {
322 auto hmacInput = std::vector<unsigned char>();
323 hmacInput.reserve(static_cast<std::size_t>(aes256cbcIvSize) + rawData.size());
324 hmacInput.insert(hmacInput.end(), iv, iv + aes256cbcIvSize);
325 hmacInput.insert(hmacInput.end(), rawData.begin(), rawData.end());
326 const auto expectedTag = Util::OpenSsl::computeHmacSha256(
327 password.data, Util::OpenSsl::Sha256Sum::size, hmacInput.data(), hmacInput.size());
328 if (CRYPTO_memcmp(authTag.data(), expectedTag.data, authTagSize) != 0) {
329 throw CryptoException("Authentication failed: data integrity check failed (wrong password or file corrupted).");
330 }
331 }
332
333 // initiate ctx, decrypt data
334 EVP_CIPHER_CTX *ctx = nullptr;
335 decryptedData.resize(remainingSize + aes256additionalBufferSize);
336 auto outlen1 = int(), outlen2 = int();
337 if ((ctx = EVP_CIPHER_CTX_new()) == nullptr || EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, password.data, iv) != 1
338 || EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()), &outlen1,
339 reinterpret_cast<unsigned char *>(rawData.data()), static_cast<int>(remainingSize))
340 != 1
341 || EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()) + outlen1, &outlen2) != 1) {
342 // handle decryption error
343 if (ctx) {
344 EVP_CIPHER_CTX_free(ctx);
345 }
346 auto msg = std::string();
347 auto errorCode = ERR_get_error();
348 while (errorCode) {
349 if (!msg.empty()) {
350 msg += "\n";
351 }
352 msg += ERR_error_string(errorCode, nullptr);
353 errorCode = ERR_get_error();
354 }
355 throw CryptoException(std::move(msg));
356 }
357
358 if (ctx) {
359 EVP_CIPHER_CTX_free(ctx);
360 }
361 const auto decryptedSize = outlen1 + outlen2;
362 if (decryptedSize < 0) {
363 throw CryptoException("Decrypted size is negative.");
364 }
365 remainingSize = static_cast<size_t>(decryptedSize);
366 if (!remainingSize) {
367 throw ParsingException("Decrypted buffer is empty.");
368 }
369
370 } else {
371 // use raw data directly if not encrypted
372 decryptedData.swap(rawData);
373 }
374
375 // decompress
376 if (compressionUsed) {
377 if (remainingSize < 8) {
378 throw ParsingException("File is truncated (decompressed size expected).");
379 }
380 if (remainingSize > numeric_limits<uLongf>::max()) {
381 throw CryptoException("Size exceeds limit.");
382 }
383 const auto rawDecompressedSize = LE::toUInt64(decryptedData.data());
384 if (rawDecompressedSize > numeric_limits<uLongf>::max()) {
385 throw ParsingException("Decompressed size exceeds limit.");
386 }
387 auto decompressedSize = static_cast<uLongf>(rawDecompressedSize);
388 rawData.resize(decompressedSize);
389 switch (uncompress(reinterpret_cast<Bytef *>(rawData.data()), &decompressedSize, reinterpret_cast<Bytef *>(decryptedData.data() + 8),
390 static_cast<uLongf>(remainingSize - 8))) {
391 case Z_MEM_ERROR:
392 throw ParsingException("Decompressing failed. The source buffer was too small.");
393 case Z_BUF_ERROR:
394 throw ParsingException("Decompressing failed. The destination buffer was too small.");
395 case Z_DATA_ERROR:
396 throw ParsingException("Decompressing failed. The input data was corrupted or incomplete.");
397 case Z_OK:
398 decryptedData.swap(rawData);
399 remainingSize = decompressedSize;
400 }
401 }
402 if (!remainingSize) {
403 throw ParsingException("Decompressed buffer is empty.");
404 }
405
406 // parse contents
407 auto decryptedStream = std::stringstream(stringstream::in | stringstream::out | stringstream::binary);
408 decryptedStream.exceptions(ios_base::failbit | ios_base::badbit);
409 try {
410#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
411 decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(), static_cast<streamsize>(remainingSize));
412#else
413 decryptedStream.write(decryptedData.data(), static_cast<streamsize>(remainingSize));
414#endif
415 if (m_version >= 0x5u) {
416 BinaryReader reader(&decryptedStream);
417 const auto extendedHeaderSize = reader.readUInt16BE();
418 m_encryptedExtendedHeader = reader.readString(extendedHeaderSize);
419 } else {
420 m_encryptedExtendedHeader.clear();
421 }
422 m_rootEntry.reset(new NodeEntry(decryptedStream));
423 } catch (const std::ios_base::failure &failure) {
424 if (decryptedStream.eof()) {
425 throw ParsingException("The file seems to be truncated.");
426 }
427 throw ParsingException(argsToString("An IO error occurred when reading internal buffer: ", failure.what()));
428 }
429}
430
436{
438 return 0x6U; // password hashing requires at least version 6
439 }
440 if (!m_encryptedExtendedHeader.empty()) {
441 return 0x5U; // encrypted extended header requires at least version 5
442 }
443 if (!m_extendedHeader.empty() || (options & PasswordFileSaveFlags::AuthenticationTag)) {
444 return 0x4U; // regular extended header requires at least version 4
445 }
446 return 0x3U; // lowest supported version by the serializer
447}
448
458{
459 if (!m_rootEntry) {
460 throw runtime_error("Root entry has not been created.");
461 }
462
463 // use already opened and writable file; otherwise re-open the file
464 auto fileSize = std::size_t();
465 if (m_file.good() && m_file.is_open() && !(m_openOptions & PasswordFileOpenFlags::ReadOnly)) {
466 fileSize = size();
467 m_file.seekp(0);
468 } else {
469 m_file.clear();
470 if (m_file.is_open()) {
471 m_file.close();
472 }
473 try {
474 m_file.open(m_path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary);
475 } catch (const ios_base::failure &) {
476 // try to create a new file if configured via \a options
478 throw;
479 }
480 m_file.open(m_path, ios_base::out | ios_base::trunc | ios_base::binary);
481 }
482 }
483
484 // write entries
485 write(options);
486 m_file.flush();
487
488 // truncate file if it became smaller
489 const auto newSize = static_cast<std::size_t>(m_file.tellp());
490 if (fileSize && newSize < fileSize) {
491 m_file.close();
492 std::filesystem::resize_file(makeNativePath(m_path), newSize);
493 }
494}
495
504{
505 if (!m_rootEntry) {
506 throw runtime_error("Root entry has not been created.");
507 }
508
509 // write magic number
510 m_fwriter.writeUInt32LE(0x7770616DU);
511
512 // write version
513 const auto version = mininumVersion(options);
514 m_fwriter.writeUInt32LE(version);
515
516 // write flags
517 auto flags = std::uint8_t{0x00};
518 if (options & PasswordFileSaveFlags::Encryption) {
519 flags |= 0x80 | 0x40;
520 }
522 flags |= 0x20;
523 }
525 flags |= 0x10;
526 }
527 m_fwriter.writeByte(flags);
528
529 // serialize root entry and descendants
530 stringstream buffstr(stringstream::in | stringstream::out | stringstream::binary);
531 buffstr.exceptions(ios_base::failbit | ios_base::badbit);
532
533 // write encrypted extended header
534 if (version >= 0x5U) {
535 if (m_encryptedExtendedHeader.size() > numeric_limits<std::uint16_t>::max()) {
536 throw runtime_error("Encrypted extended header exceeds maximum size.");
537 }
538 BinaryWriter buffstrWriter(&buffstr);
539 buffstrWriter.writeUInt16BE(static_cast<std::uint16_t>(m_encryptedExtendedHeader.size()));
540 buffstrWriter.writeString(m_encryptedExtendedHeader);
541 }
542 m_rootEntry->make(buffstr);
543 buffstr.seekp(0, ios_base::end);
544 auto size = static_cast<std::size_t>(buffstr.tellp());
545 if (size > std::numeric_limits<uLong>::max()) {
546 throw std::runtime_error("File size exceeds maximum size.");
547 }
548
549 // write the data to a buffer
550 buffstr.seekg(0);
551 auto decryptedData = std::vector<char>(size, 0);
552 buffstr.read(decryptedData.data(), static_cast<streamoff>(size));
553 auto encryptedData = std::vector<char>();
554
555 // compress data
557 auto compressedSize = static_cast<uLongf>(compressBound(static_cast<uLong>(size)));
558 encryptedData.resize(8 + compressedSize);
559 LE::getBytes(static_cast<std::uint64_t>(size), encryptedData.data());
560 switch (compress(reinterpret_cast<Bytef *>(encryptedData.data() + 8), &compressedSize, reinterpret_cast<Bytef *>(decryptedData.data()),
561 static_cast<uLong>(size))) {
562 case Z_MEM_ERROR:
563 throw runtime_error("Compressing failed. The source buffer was too small.");
564 case Z_BUF_ERROR:
565 throw runtime_error("Compressing failed. The destination buffer was too small.");
566 case Z_OK:
567 encryptedData.swap(decryptedData); // compression successful
568 size = 8 + compressedSize;
569 }
570 }
571
572 if (size > numeric_limits<int>::max()) {
573 throw CryptoException("size exceeds limit");
574 }
575
576 // define variables for encryption
578 const auto hashCount = (options & PasswordFileSaveFlags::PasswordHashing) ? Util::OpenSsl::generateRandomNumber(100000, 500000) : 0u;
579 EVP_CIPHER_CTX *ctx = nullptr;
580 unsigned char iv[aes256cbcIvSize];
581 auto ciphertextSize = std::size_t();
582
583 if (options & PasswordFileSaveFlags::Encryption) {
584 // prepare password
585 if (hashCount) {
586 // hash password a few times
587 password = Util::OpenSsl::computeSha256Sum(reinterpret_cast<unsigned const char *>(m_password.data()), m_password.size());
588 for (uint32_t i = 1; i < hashCount; ++i) {
590 }
591 } else {
592 m_password.copy(reinterpret_cast<char *>(password.data), Util::OpenSsl::Sha256Sum::size);
593 }
594
595 // initiate ctx, encrypt data
596 int outlen1, outlen2;
597 encryptedData.resize(size + aes256additionalBufferSize);
598 if (RAND_bytes(iv, aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) == nullptr
599 || EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, password.data, iv) != 1
600 || EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()), &outlen1,
601 reinterpret_cast<unsigned char *>(decryptedData.data()), static_cast<int>(size))
602 != 1
603 || EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()) + outlen1, &outlen2) != 1) {
604 // handle encryption error
605 if (ctx) {
606 EVP_CIPHER_CTX_free(ctx);
607 }
608 string msg;
609 auto errorCode = ERR_get_error();
610 while (errorCode) {
611 if (!msg.empty()) {
612 msg += "\n";
613 }
614 msg += ERR_error_string(errorCode, nullptr);
615 errorCode = ERR_get_error();
616 }
617 throw CryptoException(std::move(msg));
618 }
619 if (ctx) {
620 EVP_CIPHER_CTX_free(ctx);
621 }
622 ciphertextSize = static_cast<std::size_t>(outlen1 + outlen2);
623 }
624
625 // write extended header
626 if (version >= 0x4U) {
627 auto extendedHeaderSize = m_extendedHeader.size();
629 extendedHeaderSize += extendedAuthTagSize;
630 }
631 if (extendedHeaderSize > numeric_limits<std::uint16_t>::max()) {
632 throw runtime_error("Extended header exceeds maximum size.");
633 }
634 m_fwriter.writeUInt16BE(static_cast<std::uint16_t>(extendedHeaderSize));
635
636 // write HMAC-SHA256 authentication tag
638 vector<unsigned char> hmacInput;
639 hmacInput.reserve(static_cast<std::size_t>(aes256cbcIvSize) + ciphertextSize);
640 hmacInput.insert(hmacInput.end(), iv, iv + aes256cbcIvSize);
641 hmacInput.insert(hmacInput.end(), reinterpret_cast<const unsigned char *>(encryptedData.data()),
642 reinterpret_cast<const unsigned char *>(encryptedData.data()) + ciphertextSize);
643 const auto hmacTag = Util::OpenSsl::computeHmacSha256(password.data, authTagSize, hmacInput.data(), hmacInput.size());
644 m_fwriter.writeUInt32BE(static_cast<std::uint32_t>(ExtendedHeaderFieldIds::AuthTag));
645 m_file.write(reinterpret_cast<const char *>(hmacTag.data), authTagSize);
646 }
647
648 m_fwriter.writeString(m_extendedHeader);
649 }
650
651 // write data to file
652 if (options & PasswordFileSaveFlags::Encryption) {
653 if (version >= 0x6U) {
654 m_fwriter.writeUInt32BE(hashCount);
655 }
656 m_file.write(reinterpret_cast<char *>(iv), aes256cbcIvSize);
657 m_file.write(reinterpret_cast<const char *>(encryptedData.data()), static_cast<streamsize>(ciphertextSize));
658 } else {
659 m_file.write(decryptedData.data(), static_cast<streamsize>(size));
660 }
661 m_file.flush();
662}
663
668{
669 m_rootEntry.reset();
670}
671
676{
677 close();
678 clearPath();
680 clearEntries();
681 m_openOptions = PasswordFileOpenFlags::None;
682 m_extendedHeader.clear();
683 m_encryptedExtendedHeader.clear();
684}
685
691void PasswordFile::exportToTextfile(const string &targetPath) const
692{
693 if (!m_rootEntry) {
694 throw runtime_error("Root entry has not been created.");
695 }
696 NativeFileStream output;
697 output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
698 output.open(targetPath, std::ios_base::out);
699 const auto printIndention = [&output](int level) {
700 for (int i = 0; i < level; ++i) {
701 output << " ";
702 }
703 };
704 auto printNode = std::function<void(const Entry *entry, int level)>();
705 printNode = [&output, &printNode, &printIndention](const Entry *entry, int level) {
706 printIndention(level);
707 output << " - " << entry->label() << endl;
708 switch (entry->type()) {
709 case EntryType::Node:
710 for (const Entry *child : static_cast<const NodeEntry *>(entry)->children()) {
711 printNode(child, level + 1);
712 }
713 break;
715 for (const Field &field : static_cast<const AccountEntry *>(entry)->fields()) {
716 printIndention(level);
717 output << " " << field.name();
718 for (auto i = field.name().length(); i < 15; ++i) {
719 output << ' ';
720 }
721 output << field.value() << endl;
722 }
723 }
724 };
725 printNode(m_rootEntry.get(), 0);
726 output.close();
727}
728
734{
735 if (!isOpen()) {
736 open();
737 }
738
739 // skip if the current file is empty anyways
740 if (!size()) {
741 return;
742 }
743
744 // reopen the file if it is newly created and hence still in write-only mode
745 if (m_openOptions & PasswordFileOpenFlags::New) {
746 auto flags = m_openOptions;
748 }
749
750 m_file.seekg(0);
751 auto backupFile = NativeFileStream(m_path + ".backup", ios::out | ios::trunc | ios::binary);
752 backupFile.exceptions(ios_base::failbit | ios_base::badbit);
753 backupFile << m_file.rdbuf();
754 backupFile.close();
755}
756
763{
764 return m_rootEntry != nullptr;
765}
766
771{
772 return m_rootEntry.get();
773}
774
779{
780 return m_rootEntry.get();
781}
782
787{
788 if (m_file.is_open()) {
789 m_file.close();
790 }
791 m_file.clear();
792}
793
797void PasswordFile::setPath(const string &value)
798{
799 close();
800 m_path = value;
801
802 // support "file://" protocol
803 if (startsWith(m_path, "file:")) {
804 m_path = m_path.substr(5);
805 }
806}
807
814{
815 if (!isOpen()) {
816 return false;
817 }
818 m_file.seekg(0);
819
820 // check magic number
821 if (m_freader.readUInt32LE() != 0x7770616DU) {
822 return false;
823 }
824
825 // check version
826 const auto version = m_freader.readUInt32LE();
827 if (version == 0x1U || version == 0x2U) {
828 return true;
829 } else if (version >= 0x3U) {
830 return m_freader.readByte() & 0x80;
831 } else {
832 return false;
833 }
834}
835
840{
841 if (!isOpen()) {
842 return 0;
843 }
844 m_file.seekg(0, ios::end);
845 return static_cast<size_t>(m_file.tellg());
846}
847
852{
853 auto result = std::string("<table>");
854 if (!m_path.empty()) {
855 result += argsToString("<tr><td>Path:</td><td>", m_path, "</td></tr>");
856 }
857 result += argsToString("<tr><td>Version:</td><td>", m_version, "</td></tr>");
858 const auto minVersion = mininumVersion(saveOptions);
859 if (m_version != minVersion) {
860 result += argsToString("<tr><td></td><td>(on disk, after saving: ", minVersion, ")</td></tr>");
861 }
862 result += argsToString("<tr><td>Features:</td><td>", flagsToString(m_saveOptions), "</td></tr>");
863 if (m_saveOptions != saveOptions) {
864 result += argsToString("<tr><td></td><td>(on disk, after saving: ", flagsToString(saveOptions), ")</td></tr>");
865 }
866 const auto stats = m_rootEntry ? m_rootEntry->computeStatistics() : EntryStatistics();
867 result += argsToString("<tr><td>Number of categories:</td><td>", stats.nodeCount, "</td></tr><tr><td>Number of accounts:</td><td>",
868 stats.accountCount, "</td></tr><tr><td>Number of fields:</td><td>", stats.fieldCount, "</td></tr></table>");
869 return result;
870}
871
876{
877 auto options = std::vector<std::string>();
879 options.emplace_back("read-only");
880 }
881 if (options.empty()) {
882 options.emplace_back("none");
883 }
884 return joinStrings(options, ", ");
885}
886
891{
892 auto options = std::vector<std::string>();
893 options.reserve(3);
895 options.emplace_back("encryption");
896 }
898 options.emplace_back("compression");
899 }
901 options.emplace_back("password hashing");
902 }
904 options.emplace_back("HMAC");
905 }
906 if (options.empty()) {
907 options.emplace_back("none");
908 }
909 return joinStrings(options, ", ");
910}
911
912} // namespace Io
The exception that is thrown when a parsing error occurs.
Definition entry.h:170
The exception that is thrown when an encryption/decryption error occurs.
Instances of the Entry class form a hierarchic data structure used to store account information.
Definition entry.h:30
The Field class holds field information which consists of a name and a value and is able to serialize...
Definition field.h:15
The NodeEntry class acts as parent for other entries.
Definition entry.h:114
The exception that is thrown when a parsing error occurs.
const NodeEntry * rootEntry() const
Returns the root entry if present or nullptr otherwise.
bool isOpen() const
Returns an indication whether the file is open.
void clear()
Closes the file if opened.
void opened()
Handles the file being opened.
void clearEntries()
Removes the root element if one is present.
void open(PasswordFileOpenFlags options=PasswordFileOpenFlags::Default)
Opens the file.
bool hasRootEntry() const
Returns an indication whether a root entry is present.
void create()
Creates the file.
void exportToTextfile(const std::string &targetPath) const
Writes the current root entry to a plain text file.
void load()
Reads the contents of the file.
void close()
Closes the file if currently opened.
std::uint32_t version() const
Returns the file version used the last time when saving the file (the version of the file as it is on...
void clearPassword()
Clears the current password.
std::string summary(PasswordFileSaveFlags saveOptions) const
Returns a summary about the file (version, used features, statistics).
PasswordFile()
Constructs a new password file.
const std::string & password() const
Returns the current password.
void setPassword(const std::string &password)
Sets the current password.
const std::string & path() const
Returns the current file path.
bool isEncryptionUsed()
Returns an indication whether encryption is used and the file is open; returns always false otherwise...
std::size_t size()
Returns the size of the file if the file is open; otherwise returns zero.
~PasswordFile()
Closes the file if still opened and destroys the instance.
void save(PasswordFileSaveFlags options=PasswordFileSaveFlags::Default)
Writes the current root entry to the file under path() replacing its previous contents.
void setPath(const std::string &value)
Sets the current file path.
std::uint32_t mininumVersion(PasswordFileSaveFlags options) const
Returns the minimum file version required to write the current instance with the specified options.
void write(PasswordFileSaveFlags options=PasswordFileSaveFlags::Default)
Writes the current root entry to the file which is assumed to be opened and writeable.
void generateRootEntry()
Generates a new root entry for the file.
void clearPath()
Clears the current path.
void doBackup()
Creates a backup of the file.
PasswordFileSaveFlags saveOptions() const
Returns the save options used the last time when saving the file.
Contains all IO related classes.
PASSWORD_FILE_EXPORT std::string flagsToString(PasswordFileOpenFlags flags)
Returns a comma-separated string for the specified flags.
constexpr auto authTagSize
constexpr auto extendedAuthTagSize
ExtendedHeaderFieldIds
constexpr auto aes256cbcIvSize
PasswordFileSaveFlags
constexpr auto aes256additionalBufferSize
constexpr auto aes256blockSize
PasswordFileOpenFlags
constexpr auto extendedHeaderFieldIdSize
PASSWORD_FILE_EXPORT std::uint32_t generateRandomNumber(std::uint32_t min, std::uint32_t max)
Generates a random number using OpenSSL.
Definition openssl.cpp:153
PASSWORD_FILE_EXPORT Sha256Sum computeHmacSha256(const unsigned char *key, std::size_t keySize, const unsigned char *data, std::size_t dataSize)
Computes an HMAC-SHA256 using OpenSSL.
Definition openssl.cpp:140
PASSWORD_FILE_EXPORT Sha256Sum computeSha256Sum(const unsigned char *buffer, std::size_t size)
Computes a SHA-256 sum using OpenSSL.
Definition openssl.cpp:130
static constexpr std::size_t size
Definition openssl.h:22