Passwordfile library: io/passwordfile.cpp Source File

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"
4#include "./parsingexception.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
35 constexpr auto aes256cbcIvSize = 16U;
36 constexpr auto aes256blockSize = 32U;
37 constexpr auto aes256additionalBufferSize = aes256blockSize * 2U;
38
39 enum class ExtendedHeaderFieldIds : std::uint32_t {
40 AuthTag = 0x686D6163U,
41};
42
43 constexpr auto authTagSize = Util::OpenSsl::Sha256Sum::size; // HMAC-SHA256
44 constexpr auto extendedHeaderFieldIdSize = sizeof(std::underlying_type<ExtendedHeaderFieldIds>::type);
45 constexpr auto extendedAuthTagSize = extendedHeaderFieldIdSize + authTagSize;
46
52
56PasswordFile::PasswordFile()
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);
64 clearPassword();
65}
66
70 PasswordFile::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);
78 setPath(path);
79 setPassword(password);
80}
81
85 PasswordFile::PasswordFile(const PasswordFile &other)
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
103 PasswordFile::PasswordFile(PasswordFile &&other)
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
121PasswordFile::~PasswordFile()
122{
123}
124
129 void PasswordFile::open(PasswordFileOpenFlags options)
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
149 void PasswordFile::opened()
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
162 void PasswordFile::generateRootEntry()
163{
164 if (!m_rootEntry) {
165 m_rootEntry.reset(new NodeEntry("accounts"));
166 }
167}
168
173 void PasswordFile::create()
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
191 void PasswordFile::load()
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)) {
220 m_saveOptions |= PasswordFileSaveFlags::AuthenticationTag;
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())) {
236 case ExtendedHeaderFieldIds::AuthTag:
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) {
281 m_saveOptions |= PasswordFileSaveFlags::PasswordHashing;
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
309 auto password = Util::OpenSsl::Sha256Sum();
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) {
314 password = Util::OpenSsl::computeSha256Sum(password.data, Util::OpenSsl::Sha256Sum::size);
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
435std::uint32_t PasswordFile::mininumVersion(PasswordFileSaveFlags options) const
436{
437 if (options & PasswordFileSaveFlags::PasswordHashing) {
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
457 void PasswordFile::save(PasswordFileSaveFlags options)
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
477 if (!(options & PasswordFileSaveFlags::AllowToCreateNewFile)) {
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
503 void PasswordFile::write(PasswordFileSaveFlags options)
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 }
521 if (options & PasswordFileSaveFlags::Compression) {
522 flags |= 0x20;
523 }
524 if (options & PasswordFileSaveFlags::AuthenticationTag) {
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
556 if (options & PasswordFileSaveFlags::Compression) {
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
577 auto password = Util::OpenSsl::Sha256Sum();
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) {
589 password = Util::OpenSsl::computeSha256Sum(password.data, Util::OpenSsl::Sha256Sum::size);
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();
628 if (options & PasswordFileSaveFlags::AuthenticationTag) {
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
637 if (options & PasswordFileSaveFlags::AuthenticationTag) {
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
667 void PasswordFile::clearEntries()
668{
669 m_rootEntry.reset();
670}
671
675 void PasswordFile::clear()
676{
677 close();
678 clearPath();
679 clearPassword();
680 clearEntries();
681 m_openOptions = PasswordFileOpenFlags::None;
682 m_extendedHeader.clear();
683 m_encryptedExtendedHeader.clear();
684}
685
691 void 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;
714 case EntryType::Account:
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
733 void PasswordFile::doBackup()
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;
747 open(flags -= PasswordFileOpenFlags::New);
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
762 bool PasswordFile::hasRootEntry() const
763{
764 return m_rootEntry != nullptr;
765}
766
770 const NodeEntry *PasswordFile::rootEntry() const
771{
772 return m_rootEntry.get();
773}
774
778 NodeEntry *PasswordFile::rootEntry()
779{
780 return m_rootEntry.get();
781}
782
786 void PasswordFile::close()
787{
788 if (m_file.is_open()) {
789 m_file.close();
790 }
791 m_file.clear();
792}
793
797 void 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
813 bool PasswordFile::isEncryptionUsed()
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
839 size_t PasswordFile::size()
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
851 string PasswordFile::summary(PasswordFileSaveFlags saveOptions) const
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
875 string flagsToString(PasswordFileOpenFlags flags)
876{
877 auto options = std::vector<std::string>();
878 if (flags & PasswordFileOpenFlags::ReadOnly) {
879 options.emplace_back("read-only");
880 }
881 if (options.empty()) {
882 options.emplace_back("none");
883 }
884 return joinStrings(options, ", ");
885}
886
890 string flagsToString(PasswordFileSaveFlags flags)
891{
892 auto options = std::vector<std::string>();
893 options.reserve(3);
894 if (flags & PasswordFileSaveFlags::Encryption) {
895 options.emplace_back("encryption");
896 }
897 if (flags & PasswordFileSaveFlags::Compression) {
898 options.emplace_back("compression");
899 }
900 if (flags & PasswordFileSaveFlags::PasswordHashing) {
901 options.emplace_back("password hashing");
902 }
903 if (flags & PasswordFileSaveFlags::AuthenticationTag) {
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
Io::AccountEntry
The exception that is thrown when a parsing error occurs.
Definition entry.h:170
Io::CryptoException
The exception that is thrown when an encryption/decryption error occurs.
Definition cryptoexception.h:11
Io::Entry
Instances of the Entry class form a hierarchic data structure used to store account information.
Definition entry.h:30
Io::Field
The Field class holds field information which consists of a name and a value and is able to serialize...
Definition field.h:15
Io::NodeEntry
The NodeEntry class acts as parent for other entries.
Definition entry.h:114
Io::ParsingException
The exception that is thrown when a parsing error occurs.
Definition parsingexception.h:11
Io::PasswordFile::rootEntry
const NodeEntry * rootEntry() const
Returns the root entry if present or nullptr otherwise.
Definition passwordfile.cpp:770
Io::PasswordFile::isOpen
bool isOpen() const
Returns an indication whether the file is open.
Definition passwordfile.h:157
Io::PasswordFile::clear
void clear()
Closes the file if opened.
Definition passwordfile.cpp:675
Io::PasswordFile::opened
void opened()
Handles the file being opened.
Definition passwordfile.cpp:149
Io::PasswordFile::clearEntries
void clearEntries()
Removes the root element if one is present.
Definition passwordfile.cpp:667
Io::PasswordFile::open
void open(PasswordFileOpenFlags options=PasswordFileOpenFlags::Default)
Opens the file.
Definition passwordfile.cpp:129
Io::PasswordFile::hasRootEntry
bool hasRootEntry() const
Returns an indication whether a root entry is present.
Definition passwordfile.cpp:762
Io::PasswordFile::create
void create()
Creates the file.
Definition passwordfile.cpp:173
Io::PasswordFile::exportToTextfile
void exportToTextfile(const std::string &targetPath) const
Writes the current root entry to a plain text file.
Definition passwordfile.cpp:691
Io::PasswordFile::load
void load()
Reads the contents of the file.
Definition passwordfile.cpp:191
Io::PasswordFile::close
void close()
Closes the file if currently opened.
Definition passwordfile.cpp:786
Io::PasswordFile::version
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...
Definition passwordfile.h:201
Io::PasswordFile::clearPassword
void clearPassword()
Clears the current password.
Definition passwordfile.h:149
Io::PasswordFile::summary
std::string summary(PasswordFileSaveFlags saveOptions) const
Returns a summary about the file (version, used features, statistics).
Definition passwordfile.cpp:851
Io::PasswordFile::PasswordFile
PasswordFile()
Constructs a new password file.
Definition passwordfile.cpp:56
Io::PasswordFile::password
const std::string & password() const
Returns the current password.
Definition passwordfile.h:125
Io::PasswordFile::setPassword
void setPassword(const std::string &password)
Sets the current password.
Definition passwordfile.h:133
Io::PasswordFile::path
const std::string & path() const
Returns the current file path.
Definition passwordfile.h:108
Io::PasswordFile::isEncryptionUsed
bool isEncryptionUsed()
Returns an indication whether encryption is used and the file is open; returns always false otherwise...
Definition passwordfile.cpp:813
Io::PasswordFile::size
std::size_t size()
Returns the size of the file if the file is open; otherwise returns zero.
Definition passwordfile.cpp:839
Io::PasswordFile::~PasswordFile
~PasswordFile()
Closes the file if still opened and destroys the instance.
Definition passwordfile.cpp:121
Io::PasswordFile::save
void save(PasswordFileSaveFlags options=PasswordFileSaveFlags::Default)
Writes the current root entry to the file under path() replacing its previous contents.
Definition passwordfile.cpp:457
Io::PasswordFile::setPath
void setPath(const std::string &value)
Sets the current file path.
Definition passwordfile.cpp:797
Io::PasswordFile::mininumVersion
std::uint32_t mininumVersion(PasswordFileSaveFlags options) const
Returns the minimum file version required to write the current instance with the specified options.
Definition passwordfile.cpp:435
Io::PasswordFile::write
void write(PasswordFileSaveFlags options=PasswordFileSaveFlags::Default)
Writes the current root entry to the file which is assumed to be opened and writeable.
Definition passwordfile.cpp:503
Io::PasswordFile::generateRootEntry
void generateRootEntry()
Generates a new root entry for the file.
Definition passwordfile.cpp:162
Io::PasswordFile::clearPath
void clearPath()
Clears the current path.
Definition passwordfile.h:116
Io::PasswordFile::doBackup
void doBackup()
Creates a backup of the file.
Definition passwordfile.cpp:733
Io::PasswordFile::saveOptions
PasswordFileSaveFlags saveOptions() const
Returns the save options used the last time when saving the file.
Definition passwordfile.h:217
cryptoexception.h
entry.h
CppUtilities
Definition openssl.h:13
Io
Contains all IO related classes.
Definition cryptoexception.h:9
Io::flagsToString
PASSWORD_FILE_EXPORT std::string flagsToString(PasswordFileOpenFlags flags)
Returns a comma-separated string for the specified flags.
Definition passwordfile.cpp:875
Io::authTagSize
constexpr auto authTagSize
Definition passwordfile.cpp:43
Io::extendedAuthTagSize
constexpr auto extendedAuthTagSize
Definition passwordfile.cpp:45
Io::ExtendedHeaderFieldIds
ExtendedHeaderFieldIds
Definition passwordfile.cpp:39
Io::ExtendedHeaderFieldIds::AuthTag
@ AuthTag
Definition passwordfile.cpp:40
Io::EntryType::Account
@ Account
Definition entry.h:19
Io::EntryType::Node
@ Node
Definition entry.h:18
Io::aes256cbcIvSize
constexpr auto aes256cbcIvSize
Definition passwordfile.cpp:35
Io::PasswordFileSaveFlags
PasswordFileSaveFlags
Definition passwordfile.h:28
Io::PasswordFileSaveFlags::None
@ None
Definition passwordfile.h:29
Io::PasswordFileSaveFlags::Compression
@ Compression
Definition passwordfile.h:31
Io::PasswordFileSaveFlags::AllowToCreateNewFile
@ AllowToCreateNewFile
Definition passwordfile.h:33
Io::PasswordFileSaveFlags::AuthenticationTag
@ AuthenticationTag
Definition passwordfile.h:34
Io::PasswordFileSaveFlags::PasswordHashing
@ PasswordHashing
Definition passwordfile.h:32
Io::PasswordFileSaveFlags::Encryption
@ Encryption
Definition passwordfile.h:30
Io::aes256additionalBufferSize
constexpr auto aes256additionalBufferSize
Definition passwordfile.cpp:37
Io::aes256blockSize
constexpr auto aes256blockSize
Definition passwordfile.cpp:36
Io::PasswordFileOpenFlags
PasswordFileOpenFlags
Definition passwordfile.h:19
Io::PasswordFileOpenFlags::New
@ New
Definition passwordfile.h:22
Io::PasswordFileOpenFlags::ReadOnly
@ ReadOnly
Definition passwordfile.h:21
Io::PasswordFileOpenFlags::None
@ None
Definition passwordfile.h:20
Io::extendedHeaderFieldIdSize
constexpr auto extendedHeaderFieldIdSize
Definition passwordfile.cpp:44
Util::OpenSsl::generateRandomNumber
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
Util::OpenSsl::computeHmacSha256
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
Util::OpenSsl::computeSha256Sum
PASSWORD_FILE_EXPORT Sha256Sum computeSha256Sum(const unsigned char *buffer, std::size_t size)
Computes a SHA-256 sum using OpenSSL.
Definition openssl.cpp:130
openssl.h
parsingexception.h
passwordfile.h
Io::EntryStatistics
Definition entry.h:22
Util::OpenSsl::Sha256Sum
Definition openssl.h:21
Util::OpenSsl::Sha256Sum::size
static constexpr std::size_t size
Definition openssl.h:22