6 #include "../util/openssl.h" 7 #include "../util/opensslrandomdevice.h" 9 #include <c++utilities/conversion/stringbuilder.h> 10 #include <c++utilities/conversion/stringconversion.h> 11 #include <c++utilities/io/catchiofailure.h> 13 #include <openssl/conf.h> 14 #include <openssl/err.h> 15 #include <openssl/evp.h> 16 #include <openssl/rand.h> 44 PasswordFile::PasswordFile()
45 : m_freader(BinaryReader(&m_file))
46 , m_fwriter(BinaryWriter(&m_file))
51 m_file.exceptions(ios_base::failbit | ios_base::badbit);
59 : m_freader(BinaryReader(&m_file))
60 , m_fwriter(BinaryWriter(&m_file))
65 m_file.exceptions(ios_base::failbit | ios_base::badbit);
74 : m_path(other.m_path)
75 , m_password(other.m_password)
76 , m_rootEntry(other.m_rootEntry ? make_unique<
NodeEntry>(*other.m_rootEntry) : nullptr)
77 , m_extendedHeader(other.m_extendedHeader)
78 , m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
79 , m_freader(BinaryReader(&m_file))
80 , m_fwriter(BinaryWriter(&m_file))
81 , m_version(other.m_version)
82 , m_openOptions(other.m_openOptions)
83 , m_saveOptions(other.m_saveOptions)
85 m_file.exceptions(ios_base::failbit | ios_base::badbit);
92 : m_path(move(other.m_path))
93 , m_password(move(other.m_password))
94 , m_rootEntry(move(other.m_rootEntry))
95 , m_extendedHeader(move(other.m_extendedHeader))
96 , m_encryptedExtendedHeader(move(other.m_encryptedExtendedHeader))
97 , m_file(move(other.m_file))
98 , m_freader(BinaryReader(&m_file))
99 , m_fwriter(BinaryWriter(&m_file))
100 , m_version(other.m_version)
101 , m_openOptions(other.m_openOptions)
102 , m_saveOptions(other.m_saveOptions)
120 if (m_path.empty()) {
121 throwIoFailure(
"Unable to open file because path is emtpy.");
125 m_openOptions = options;
136 m_file.seekg(0, ios_base::end);
137 if (m_file.tellg() == 0) {
138 throwIoFailure(
"File is empty.");
150 m_rootEntry.reset(
new NodeEntry(
"accounts"));
161 if (m_path.empty()) {
162 throwIoFailure(
"Unable to create file because path is empty.");
164 m_file.open(m_path, fstream::out | fstream::trunc | fstream::binary);
177 if (!m_file.is_open()) {
185 if (m_freader.readUInt32LE() != 0x7770616DU) {
190 m_version = m_freader.readUInt32LE();
191 if (m_version > 0x6U) {
192 throw ParsingException(argsToString(
"Version \"", m_version,
"\" is unknown. Only versions 0 to 6 are supported."));
194 if (m_version >= 0x6U) {
197 bool decrypterUsed, ivUsed, compressionUsed;
198 if (m_version >= 0x3U) {
199 const auto flags = m_freader.readByte();
200 if ((decrypterUsed = flags & 0x80)) {
203 if ((compressionUsed = flags & 0x20)) {
206 ivUsed = flags & 0x40;
208 if ((decrypterUsed = m_version >= 0x1U)) {
211 compressionUsed =
false;
212 ivUsed = m_version == 0x2U;
218 if (m_version >= 0x4U) {
219 uint16 extendedHeaderSize = m_freader.readUInt16BE();
220 m_extendedHeader = m_freader.readString(extendedHeaderSize);
222 m_extendedHeader.clear();
226 const auto headerSize =
static_cast<size_t>(m_file.tellg());
227 m_file.seekg(0, ios_base::end);
228 auto remainingSize =
static_cast<size_t>(m_file.tellg()) - headerSize;
229 m_file.seekg(static_cast<streamoff>(headerSize), ios_base::beg);
232 uint32_t hashCount = 0U;
234 if (remainingSize < 4) {
237 hashCount = m_freader.readUInt32BE();
243 if (decrypterUsed && ivUsed) {
250 if (!remainingSize) {
255 vector<char> rawData;
256 m_freader.read(rawData, static_cast<streamoff>(remainingSize));
257 vector<char> decryptedData;
259 if (remainingSize > numeric_limits<int>::max()) {
268 for (uint32_t i = 1; i < hashCount; ++i) {
276 EVP_CIPHER_CTX *ctx =
nullptr;
277 decryptedData.resize(remainingSize + 32);
278 int outlen1, outlen2;
279 if ((ctx = EVP_CIPHER_CTX_new()) ==
nullptr || EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(),
nullptr,
password.data, iv) != 1
280 || EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()), &outlen1,
281 reinterpret_cast<unsigned char *>(rawData.data()), static_cast<int>(remainingSize))
283 || EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()) + outlen1, &outlen2) != 1) {
286 EVP_CIPHER_CTX_free(ctx);
289 auto errorCode = ERR_get_error();
294 msg += ERR_error_string(errorCode,
nullptr);
295 errorCode = ERR_get_error();
301 EVP_CIPHER_CTX_free(ctx);
303 const auto decryptedSize = outlen1 + outlen2;
304 if (decryptedSize < 0) {
307 remainingSize =
static_cast<size_t>(decryptedSize);
308 if (!remainingSize) {
314 decryptedData.swap(rawData);
318 if (compressionUsed) {
319 if (remainingSize < 8) {
322 if (remainingSize > numeric_limits<uLongf>::max()) {
325 const auto rawDecompressedSize = ConversionUtilities::LE::toUInt64(decryptedData.data());
326 if (rawDecompressedSize > numeric_limits<uLongf>::max()) {
329 auto decompressedSize =
static_cast<uLongf
>(rawDecompressedSize);
330 rawData.resize(decompressedSize);
331 switch (uncompress(reinterpret_cast<Bytef *>(rawData.data()), &decompressedSize, reinterpret_cast<Bytef *>(decryptedData.data() + 8),
332 static_cast<uLongf>(remainingSize - 8))) {
334 throw ParsingException(
"Decompressing failed. The source buffer was too small.");
336 throw ParsingException(
"Decompressing failed. The destination buffer was too small.");
338 throw ParsingException(
"Decompressing failed. The input data was corrupted or incomplete.");
340 decryptedData.swap(rawData);
341 remainingSize = decompressedSize;
344 if (!remainingSize) {
349 stringstream decryptedStream(stringstream::in | stringstream::out | stringstream::binary);
350 decryptedStream.exceptions(ios_base::failbit | ios_base::badbit);
352 #ifdef _LIBCPP_VERSION 353 decryptedStream.write(decryptedData.data(),
static_cast<streamsize
>(remainingSize));
355 decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(),
static_cast<streamsize
>(remainingSize));
357 if (m_version >= 0x5u) {
358 BinaryReader reader(&decryptedStream);
359 const auto extendedHeaderSize = reader.readUInt16BE();
360 m_encryptedExtendedHeader = reader.readString(extendedHeaderSize);
362 m_encryptedExtendedHeader.clear();
364 m_rootEntry.reset(
new NodeEntry(decryptedStream));
366 const char *
const what = catchIoFailure();
367 if (decryptedStream.eof()) {
370 throw ParsingException(argsToString(
"An IO error occurred when reading internal buffer: ", what));
382 }
else if (!m_encryptedExtendedHeader.empty()) {
384 }
else if (!m_extendedHeader.empty()) {
400 throw runtime_error(
"Root entry has not been created.");
404 if (m_file.good() && m_file.is_open() && (m_file.flags() & ios_base::out)) {
409 m_file.open(m_path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary);
426 throw runtime_error(
"Root entry has not been created.");
430 m_fwriter.writeUInt32LE(0x7770616DU);
434 m_fwriter.writeUInt32LE(
version);
439 flags |= 0x80 | 0x40;
444 m_fwriter.writeByte(flags);
448 if (m_extendedHeader.size() > numeric_limits<uint16>::max()) {
449 throw runtime_error(
"Extended header exceeds maximum size.");
451 m_fwriter.writeUInt16BE(static_cast<uint16>(m_extendedHeader.size()));
452 m_fwriter.writeString(m_extendedHeader);
456 stringstream buffstr(stringstream::in | stringstream::out | stringstream::binary);
457 buffstr.exceptions(ios_base::failbit | ios_base::badbit);
461 if (m_encryptedExtendedHeader.size() > numeric_limits<uint16>::max()) {
462 throw runtime_error(
"Encrypted extended header exceeds maximum size.");
464 BinaryWriter buffstrWriter(&buffstr);
465 buffstrWriter.writeUInt16BE(static_cast<uint16>(m_encryptedExtendedHeader.size()));
466 buffstrWriter.writeString(m_encryptedExtendedHeader);
468 m_rootEntry->make(buffstr);
469 buffstr.seekp(0, ios_base::end);
470 auto size =
static_cast<size_t>(buffstr.tellp());
474 vector<char> decryptedData(
size, 0);
475 buffstr.read(decryptedData.data(),
static_cast<streamoff
>(
size));
476 vector<char> encryptedData;
480 uLongf compressedSize = compressBound(
size);
481 encryptedData.resize(8 + compressedSize);
482 ConversionUtilities::LE::getBytes(static_cast<uint64>(
size), encryptedData.data());
484 compress(reinterpret_cast<Bytef *>(encryptedData.data() + 8), &compressedSize, reinterpret_cast<Bytef *>(decryptedData.data()),
size)) {
486 throw runtime_error(
"Compressing failed. The source buffer was too small.");
488 throw runtime_error(
"Compressing failed. The destination buffer was too small.");
490 encryptedData.swap(decryptedData);
491 size = 8 + compressedSize;
495 if (
size > numeric_limits<int>::max()) {
502 m_file.write(decryptedData.data(),
static_cast<streamsize
>(
size));
512 for (uint32_t i = 1; i < hashCount; ++i) {
520 EVP_CIPHER_CTX *ctx =
nullptr;
522 int outlen1, outlen2;
523 encryptedData.resize(
size + 32);
524 if (RAND_bytes(iv,
aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) ==
nullptr 525 || EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(),
nullptr,
password.data, iv) != 1
526 || EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()), &outlen1,
527 reinterpret_cast<unsigned char *>(decryptedData.data()), static_cast<int>(
size))
529 || EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()) + outlen1, &outlen2) != 1) {
532 EVP_CIPHER_CTX_free(ctx);
535 auto errorCode = ERR_get_error();
540 msg += ERR_error_string(errorCode,
nullptr);
541 errorCode = ERR_get_error();
547 EVP_CIPHER_CTX_free(ctx);
552 m_fwriter.writeUInt32BE(hashCount);
555 m_file.write(encryptedData.data(),
static_cast<streamsize
>(outlen1 + outlen2));
576 m_extendedHeader.clear();
577 m_encryptedExtendedHeader.clear();
589 throw runtime_error(
"Root entry has not been created.");
591 fstream output(targetPath.c_str(), ios_base::out);
592 const auto printIndention = [&output](
int level) {
593 for (
int i = 0; i < level; ++i) {
597 function<void(const Entry *entry, int level)> printNode;
598 printNode = [&output, &printNode, &printIndention](
const Entry *entry,
int level) {
599 printIndention(level);
600 output <<
" - " << entry->label() << endl;
601 switch (entry->type()) {
603 for (
const Entry *child : static_cast<const NodeEntry *>(entry)->children()) {
604 printNode(child, level + 1);
608 for (
const Field &field : static_cast<const AccountEntry *>(entry)->fields()) {
609 printIndention(level);
610 output <<
" " << field.name();
611 for (
auto i = field.name().length(); i < 15; ++i) {
614 output << field.value() << endl;
618 printNode(m_rootEntry.get(), 0);
638 fstream backupFile(m_path +
".backup", ios::out | ios::trunc | ios::binary);
639 backupFile.exceptions(ios_base::failbit | ios_base::badbit);
640 backupFile << m_file.rdbuf();
651 return m_rootEntry !=
nullptr;
659 return m_rootEntry.get();
667 return m_rootEntry.get();
675 if (m_file.is_open()) {
690 if (ConversionUtilities::startsWith(m_path,
"file:")) {
691 m_path = m_path.substr(5);
708 if (m_freader.readUInt32LE() != 0x7770616DU) {
713 const auto version = m_freader.readUInt32LE();
717 return m_freader.readByte() & 0x80;
731 m_file.seekg(0, ios::end);
732 return static_cast<size_t>(m_file.tellg());
740 string result =
"<table>";
741 if (!m_path.empty()) {
742 result += argsToString(
"<tr><td>Path:</td><td>", m_path,
"</td></tr>");
744 result += argsToString(
"<tr><td>Version:</td><td>", m_version,
"</td></tr>");
746 if (m_version != minVersion) {
747 result += argsToString(
"<tr><td></td><td>(on disk, after saving: ", minVersion,
")</td></tr>");
749 result += argsToString(
"<tr><td>Features:</td><td>",
flagsToString(m_saveOptions),
"</td></tr>");
753 const auto stats = m_rootEntry ? m_rootEntry->computeStatistics() :
EntryStatistics();
754 result += argsToString(
"<tr><td>Number of categories:</td><td>", stats.nodeCount,
"</td></tr><tr><td>Number of accounts:</td><td>",
755 stats.accountCount,
"</td></tr><tr><td>Number of fields:</td><td>", stats.fieldCount,
"</td></tr></table>");
764 vector<string> options;
766 options.emplace_back(
"read-only");
768 if (options.empty()) {
769 options.emplace_back(
"none");
771 return joinStrings(options,
", ");
779 vector<string> options;
782 options.emplace_back(
"encryption");
785 options.emplace_back(
"compression");
788 options.emplace_back(
"password hashing");
790 if (options.empty()) {
791 options.emplace_back(
"none");
793 return joinStrings(options,
", ");
PasswordFile()
Constructs a new password file.
uint32 mininumVersion(PasswordFileSaveFlags options) const
Returns the minimum file version required to write the current instance with the specified options...
bool hasRootEntry() const
Returns an indication whether a root entry is present.
void clear()
Closes the file if opened.
The NodeEntry class acts as parent for other entries.
The PasswordFile class holds account information in the form of Entry and Field instances and provide...
void load()
Reads the contents of the file.
static constexpr std::size_t size
std::string PASSWORD_FILE_EXPORT flagsToString(PasswordFileOpenFlags flags)
Returns a comma-separated string for the specified flags.
void close()
Closes the file if currently opened.
void doBackup()
Creates a backup of the file.
Contains all IO related classes.
const NodeEntry * rootEntry() const
Returns the root entry if present or nullptr otherwise.
std::string summary(PasswordFileSaveFlags saveOptions) const
Returns a summary about the file (version, used features, statistics).
void open(PasswordFileOpenFlags options=PasswordFileOpenFlags::Default)
Opens the file.
Sha256Sum PASSWORD_FILE_EXPORT computeSha256Sum(const unsigned char *buffer, std::size_t size)
Computes a SHA-256 sum using OpenSSL.
void exportToTextfile(const std::string &targetPath) const
Writes the current root entry to a plain text file.
bool isOpen() const
Returns an indication whether the file is open.
The Field class holds field information which consists of a name and a value and is able to serialize...
void clearPath()
Clears the current path.
bool isEncryptionUsed()
Returns an indication whether encryption is used and the file is open; returns always false otherwise...
const unsigned int aes256cbcIvSize
~PasswordFile()
Closes the file if still opened and destroys the instance.
const std::string & path() const
Returns the current file path.
void setPassword(const std::string &password)
Sets the current password.
void setPath(const std::string &value)
Sets the current file path.
std::uint32_t PASSWORD_FILE_EXPORT generateRandomNumber(std::uint32_t min, std::uint32_t max)
void create()
Creates the file.
std::size_t size()
Returns the size of the file if the file is open; otherwise returns zero.
void generateRootEntry()
Generates a new root entry for the file.
void write(PasswordFileSaveFlags options=PasswordFileSaveFlags::Default)
Writes the current root entry to the file which is assumed to be opened and writeable.
const std::string & password() const
Returns the current password.
The exception that is thrown when an encryption/decryption error occurs.
uint32 version() const
Returns the file version used the last time when saving the file (the version of the file as it is on...
void opened()
Handles the file being opened.
The exception that is thrown when a parsing error occurs.
void save(PasswordFileSaveFlags options=PasswordFileSaveFlags::Default)
Writes the current root entry to the file under path() replacing its previous contents.
void clearPassword()
Clears the current password.
void clearEntries()
Removes the root element if one is present.
Instances of the Entry class form a hierarchic data strucutre used to store account information...
PasswordFileSaveFlags saveOptions() const
Returns the save options used the last time when saving the file.