2016-08-29 15:42:07 +02:00
|
|
|
#include "./passwordfile.h"
|
|
|
|
#include "./cryptoexception.h"
|
|
|
|
#include "./entry.h"
|
2017-05-01 03:25:30 +02:00
|
|
|
#include "./parsingexception.h"
|
2016-08-29 15:42:07 +02:00
|
|
|
|
2018-12-18 23:17:19 +01:00
|
|
|
#include "../util/openssl.h"
|
|
|
|
#include "../util/opensslrandomdevice.h"
|
|
|
|
|
2018-12-03 00:26:11 +01:00
|
|
|
#include <c++utilities/conversion/stringbuilder.h>
|
2018-12-08 19:09:17 +01:00
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
2016-08-29 15:42:07 +02:00
|
|
|
#include <c++utilities/io/catchiofailure.h>
|
|
|
|
|
|
|
|
#include <openssl/conf.h>
|
|
|
|
#include <openssl/err.h>
|
|
|
|
#include <openssl/evp.h>
|
|
|
|
#include <openssl/rand.h>
|
|
|
|
|
|
|
|
#include <zlib.h>
|
|
|
|
|
|
|
|
#include <cstring>
|
|
|
|
#include <functional>
|
2018-09-08 19:38:15 +02:00
|
|
|
#include <limits>
|
2017-05-01 03:25:30 +02:00
|
|
|
#include <memory>
|
|
|
|
#include <sstream>
|
|
|
|
#include <streambuf>
|
2016-08-29 15:42:07 +02:00
|
|
|
|
|
|
|
using namespace std;
|
2018-12-03 00:26:11 +01:00
|
|
|
using namespace ConversionUtilities;
|
2016-08-29 15:42:07 +02:00
|
|
|
using namespace IoUtilities;
|
|
|
|
|
|
|
|
namespace Io {
|
|
|
|
|
|
|
|
const unsigned int aes256cbcIvSize = 16U;
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \class PasswordFile
|
|
|
|
* \brief The PasswordFile class holds account information in the form of Entry and Field instances
|
|
|
|
* and provides methods to read and write these information to encrypted files using OpenSSL.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new password file.
|
|
|
|
*/
|
2017-05-01 03:25:30 +02:00
|
|
|
PasswordFile::PasswordFile()
|
|
|
|
: m_freader(BinaryReader(&m_file))
|
|
|
|
, m_fwriter(BinaryWriter(&m_file))
|
2018-12-21 01:12:53 +01:00
|
|
|
, m_version(0)
|
|
|
|
, m_openOptions(PasswordFileOpenFlags::None)
|
|
|
|
, m_saveOptions(PasswordFileSaveFlags::None)
|
2016-08-29 15:42:07 +02:00
|
|
|
{
|
|
|
|
m_file.exceptions(ios_base::failbit | ios_base::badbit);
|
|
|
|
clearPassword();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new password file with the specified \a path and \a password.
|
|
|
|
*/
|
2017-05-01 03:25:30 +02:00
|
|
|
PasswordFile::PasswordFile(const string &path, const string &password)
|
|
|
|
: m_freader(BinaryReader(&m_file))
|
|
|
|
, m_fwriter(BinaryWriter(&m_file))
|
2018-12-21 01:12:53 +01:00
|
|
|
, m_version(0)
|
|
|
|
, m_openOptions(PasswordFileOpenFlags::None)
|
|
|
|
, m_saveOptions(PasswordFileSaveFlags::None)
|
2016-08-29 15:42:07 +02:00
|
|
|
{
|
|
|
|
m_file.exceptions(ios_base::failbit | ios_base::badbit);
|
|
|
|
setPath(path);
|
|
|
|
setPassword(password);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a copy of another password file.
|
|
|
|
*/
|
2017-05-01 03:25:30 +02:00
|
|
|
PasswordFile::PasswordFile(const PasswordFile &other)
|
|
|
|
: m_path(other.m_path)
|
2018-12-18 23:17:19 +01:00
|
|
|
, m_password(other.m_password)
|
2018-09-05 00:32:32 +02:00
|
|
|
, m_rootEntry(other.m_rootEntry ? make_unique<NodeEntry>(*other.m_rootEntry) : nullptr)
|
|
|
|
, m_extendedHeader(other.m_extendedHeader)
|
|
|
|
, m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
|
2017-05-01 03:25:30 +02:00
|
|
|
, m_freader(BinaryReader(&m_file))
|
|
|
|
, m_fwriter(BinaryWriter(&m_file))
|
2018-12-21 01:12:53 +01:00
|
|
|
, m_version(other.m_version)
|
|
|
|
, m_openOptions(other.m_openOptions)
|
|
|
|
, m_saveOptions(other.m_saveOptions)
|
2016-08-29 15:42:07 +02:00
|
|
|
{
|
|
|
|
m_file.exceptions(ios_base::failbit | ios_base::badbit);
|
2018-09-05 00:32:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Moves the password file.
|
|
|
|
*/
|
|
|
|
PasswordFile::PasswordFile(PasswordFile &&other)
|
|
|
|
: m_path(move(other.m_path))
|
2018-12-18 23:17:19 +01:00
|
|
|
, m_password(move(other.m_password))
|
2018-09-05 00:32:32 +02:00
|
|
|
, m_rootEntry(move(other.m_rootEntry))
|
|
|
|
, m_extendedHeader(move(other.m_extendedHeader))
|
|
|
|
, m_encryptedExtendedHeader(move(other.m_encryptedExtendedHeader))
|
|
|
|
, m_file(move(other.m_file))
|
|
|
|
, m_freader(BinaryReader(&m_file))
|
|
|
|
, m_fwriter(BinaryWriter(&m_file))
|
2018-12-21 01:12:53 +01:00
|
|
|
, m_version(other.m_version)
|
|
|
|
, m_openOptions(other.m_openOptions)
|
|
|
|
, m_saveOptions(other.m_saveOptions)
|
2018-09-05 00:32:32 +02:00
|
|
|
{
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Closes the file if still opened and destroys the instance.
|
|
|
|
*/
|
|
|
|
PasswordFile::~PasswordFile()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Opens the file. Does not load the contents (see load()).
|
|
|
|
* \throws Throws ios_base::failure when an IO error occurs.
|
|
|
|
*/
|
2018-12-18 23:17:19 +01:00
|
|
|
void PasswordFile::open(PasswordFileOpenFlags options)
|
2016-08-29 15:42:07 +02:00
|
|
|
{
|
|
|
|
close();
|
2017-05-01 03:25:30 +02:00
|
|
|
if (m_path.empty()) {
|
2016-08-29 15:42:07 +02:00
|
|
|
throwIoFailure("Unable to open file because path is emtpy.");
|
|
|
|
}
|
2018-12-18 23:17:19 +01:00
|
|
|
m_file.open(
|
|
|
|
m_path, options & PasswordFileOpenFlags::ReadOnly ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary);
|
2018-12-21 17:32:31 +01:00
|
|
|
m_openOptions = options;
|
2018-09-05 00:33:01 +02:00
|
|
|
opened();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Handles the file being opened.
|
|
|
|
*
|
|
|
|
* Call this method after opening a file directly via the underlying fileStream().
|
|
|
|
*/
|
|
|
|
void PasswordFile::opened()
|
|
|
|
{
|
2016-08-29 15:42:07 +02:00
|
|
|
m_file.seekg(0, ios_base::end);
|
2017-05-01 03:25:30 +02:00
|
|
|
if (m_file.tellg() == 0) {
|
2016-08-29 15:42:07 +02:00
|
|
|
throwIoFailure("File is empty.");
|
|
|
|
} else {
|
|
|
|
m_file.seekg(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Generates a new root entry for the file.
|
|
|
|
*/
|
|
|
|
void PasswordFile::generateRootEntry()
|
|
|
|
{
|
2017-05-01 03:25:30 +02:00
|
|
|
if (!m_rootEntry) {
|
2016-08-29 15:42:07 +02:00
|
|
|
m_rootEntry.reset(new NodeEntry("accounts"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Creates the file. Does not generate a new root element (see generateRootElement()).
|
|
|
|
* \throws Throws ios_base::failure when an IO error occurs.
|
|
|
|
*/
|
|
|
|
void PasswordFile::create()
|
|
|
|
{
|
|
|
|
close();
|
2017-05-01 03:25:30 +02:00
|
|
|
if (m_path.empty()) {
|
2016-08-29 15:42:07 +02:00
|
|
|
throwIoFailure("Unable to create file because path is empty.");
|
|
|
|
}
|
|
|
|
m_file.open(m_path, fstream::out | fstream::trunc | fstream::binary);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Reads the contents of the file. Opens the file if not already opened. Replaces
|
|
|
|
* the current root entry with the new one constructed from the file contents.
|
|
|
|
* \throws Throws ios_base::failure when an IO error occurs.
|
|
|
|
* \throws Throws Io::ParsingException when a parsing error occurs.
|
|
|
|
* \throws Throws Io::CryptoException when a decryption error occurs.
|
|
|
|
* \throws Throws ConversionUtilities::ConversionException when a conversion error occurs.
|
|
|
|
*/
|
|
|
|
void PasswordFile::load()
|
|
|
|
{
|
2017-05-01 03:25:30 +02:00
|
|
|
if (!m_file.is_open()) {
|
2016-08-29 15:42:07 +02:00
|
|
|
open();
|
|
|
|
}
|
|
|
|
m_file.seekg(0);
|
2018-12-21 01:12:53 +01:00
|
|
|
m_version = 0;
|
|
|
|
m_saveOptions = PasswordFileSaveFlags::None;
|
2018-09-08 19:38:15 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// check magic number
|
2017-05-01 03:25:30 +02:00
|
|
|
if (m_freader.readUInt32LE() != 0x7770616DU) {
|
2016-08-29 15:42:07 +02:00
|
|
|
throw ParsingException("Signature not present.");
|
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// check version and flags (used in version 0x3 only)
|
2018-12-21 01:12:53 +01:00
|
|
|
m_version = m_freader.readUInt32LE();
|
|
|
|
if (m_version > 0x6U) {
|
|
|
|
throw ParsingException(argsToString("Version \"", m_version, "\" is unknown. Only versions 0 to 6 are supported."));
|
|
|
|
}
|
|
|
|
if (m_version >= 0x6U) {
|
|
|
|
m_saveOptions |= PasswordFileSaveFlags::PasswordHashing;
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
bool decrypterUsed, ivUsed, compressionUsed;
|
2018-12-21 01:12:53 +01:00
|
|
|
if (m_version >= 0x3U) {
|
2018-09-08 19:38:15 +02:00
|
|
|
const auto flags = m_freader.readByte();
|
2018-12-21 01:12:53 +01:00
|
|
|
if ((decrypterUsed = flags & 0x80)) {
|
|
|
|
m_saveOptions |= PasswordFileSaveFlags::Encryption;
|
|
|
|
}
|
|
|
|
if ((compressionUsed = flags & 0x20)) {
|
|
|
|
m_saveOptions |= PasswordFileSaveFlags::Compression;
|
|
|
|
}
|
2016-08-29 15:42:07 +02:00
|
|
|
ivUsed = flags & 0x40;
|
|
|
|
} else {
|
2018-12-21 01:12:53 +01:00
|
|
|
if ((decrypterUsed = m_version >= 0x1U)) {
|
|
|
|
m_saveOptions |= PasswordFileSaveFlags::Encryption;
|
|
|
|
}
|
2016-08-29 15:42:07 +02:00
|
|
|
compressionUsed = false;
|
2018-12-21 01:12:53 +01:00
|
|
|
ivUsed = m_version == 0x2U;
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// skip extended header
|
2018-09-08 19:38:15 +02:00
|
|
|
// (the extended header might be used in further versions to
|
|
|
|
// add additional information without breaking compatibility)
|
2018-12-21 01:12:53 +01:00
|
|
|
if (m_version >= 0x4U) {
|
2016-08-29 15:42:07 +02:00
|
|
|
uint16 extendedHeaderSize = m_freader.readUInt16BE();
|
|
|
|
m_extendedHeader = m_freader.readString(extendedHeaderSize);
|
2018-12-18 23:17:19 +01:00
|
|
|
} else {
|
|
|
|
m_extendedHeader.clear();
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// get length
|
2018-09-08 19:38:15 +02:00
|
|
|
const auto headerSize = static_cast<size_t>(m_file.tellg());
|
2016-08-29 15:42:07 +02:00
|
|
|
m_file.seekg(0, ios_base::end);
|
2018-09-08 19:38:15 +02:00
|
|
|
auto remainingSize = static_cast<size_t>(m_file.tellg()) - headerSize;
|
2018-09-07 01:14:21 +02:00
|
|
|
m_file.seekg(static_cast<streamoff>(headerSize), ios_base::beg);
|
2018-09-08 19:38:15 +02:00
|
|
|
|
2018-12-18 23:17:19 +01:00
|
|
|
// read hash count
|
|
|
|
uint32_t hashCount = 0U;
|
2018-12-21 01:12:53 +01:00
|
|
|
if ((m_saveOptions & PasswordFileSaveFlags::PasswordHashing) && decrypterUsed) {
|
2018-12-18 23:17:19 +01:00
|
|
|
if (remainingSize < 4) {
|
|
|
|
throw ParsingException("Hash count truncated.");
|
|
|
|
}
|
|
|
|
hashCount = m_freader.readUInt32BE();
|
|
|
|
remainingSize -= 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read IV
|
2017-05-01 03:25:30 +02:00
|
|
|
unsigned char iv[aes256cbcIvSize] = { 0 };
|
|
|
|
if (decrypterUsed && ivUsed) {
|
2018-09-08 19:38:15 +02:00
|
|
|
if (remainingSize < aes256cbcIvSize) {
|
|
|
|
throw ParsingException("Initiation vector is truncated.");
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
|
|
|
m_file.read(reinterpret_cast<char *>(iv), aes256cbcIvSize);
|
2018-09-08 19:38:15 +02:00
|
|
|
remainingSize -= aes256cbcIvSize;
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
if (!remainingSize) {
|
2016-08-29 15:42:07 +02:00
|
|
|
throw ParsingException("No contents found.");
|
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// decrypt contents
|
2018-09-08 19:38:15 +02:00
|
|
|
vector<char> rawData;
|
|
|
|
m_freader.read(rawData, static_cast<streamoff>(remainingSize));
|
|
|
|
vector<char> decryptedData;
|
2017-05-01 03:25:30 +02:00
|
|
|
if (decrypterUsed) {
|
2018-09-08 19:38:15 +02:00
|
|
|
if (remainingSize > numeric_limits<int>::max()) {
|
2018-09-07 01:14:21 +02:00
|
|
|
throw CryptoException("Size exceeds limit.");
|
|
|
|
}
|
|
|
|
|
2018-12-18 23:17:19 +01:00
|
|
|
// prepare password
|
|
|
|
Util::OpenSsl::Sha256Sum password;
|
|
|
|
if (hashCount) {
|
|
|
|
// hash the password as often as it has been hashed when writing the file
|
|
|
|
password = Util::OpenSsl::computeSha256Sum(reinterpret_cast<unsigned const char *>(m_password.data()), m_password.size());
|
|
|
|
for (uint32_t i = 1; i < hashCount; ++i) {
|
|
|
|
password = Util::OpenSsl::computeSha256Sum(password.data, Util::OpenSsl::Sha256Sum::size);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m_password.copy(reinterpret_cast<char *>(password.data), Util::OpenSsl::Sha256Sum::size);
|
|
|
|
}
|
|
|
|
|
2018-09-08 19:38:15 +02:00
|
|
|
// initiate ctx, decrypt data
|
2016-08-29 15:42:07 +02:00
|
|
|
EVP_CIPHER_CTX *ctx = nullptr;
|
2018-09-08 19:38:15 +02:00
|
|
|
decryptedData.resize(remainingSize + 32);
|
2016-08-29 15:42:07 +02:00
|
|
|
int outlen1, outlen2;
|
2018-12-18 23:17:19 +01:00
|
|
|
if ((ctx = EVP_CIPHER_CTX_new()) == nullptr || EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, password.data, iv) != 1
|
2018-09-08 19:38:15 +02:00
|
|
|
|| EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()), &outlen1,
|
|
|
|
reinterpret_cast<unsigned char *>(rawData.data()), static_cast<int>(remainingSize))
|
2017-05-01 03:25:30 +02:00
|
|
|
!= 1
|
2018-09-08 19:38:15 +02:00
|
|
|
|| EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()) + outlen1, &outlen2) != 1) {
|
|
|
|
// handle decryption error
|
2017-05-01 03:25:30 +02:00
|
|
|
if (ctx) {
|
2016-08-29 15:42:07 +02:00
|
|
|
EVP_CIPHER_CTX_free(ctx);
|
|
|
|
}
|
|
|
|
string msg;
|
2018-09-07 01:14:21 +02:00
|
|
|
auto errorCode = ERR_get_error();
|
|
|
|
while (errorCode) {
|
2017-05-01 03:25:30 +02:00
|
|
|
if (!msg.empty()) {
|
2016-08-29 15:42:07 +02:00
|
|
|
msg += "\n";
|
|
|
|
}
|
2018-09-07 01:14:21 +02:00
|
|
|
msg += ERR_error_string(errorCode, nullptr);
|
2016-08-29 15:42:07 +02:00
|
|
|
errorCode = ERR_get_error();
|
|
|
|
}
|
2018-12-21 17:31:24 +01:00
|
|
|
throw CryptoException(move(msg));
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
|
|
|
|
if (ctx) {
|
|
|
|
EVP_CIPHER_CTX_free(ctx);
|
|
|
|
}
|
|
|
|
const auto decryptedSize = outlen1 + outlen2;
|
|
|
|
if (decryptedSize < 0) {
|
|
|
|
throw CryptoException("Decrypted size is negative.");
|
|
|
|
}
|
|
|
|
remainingSize = static_cast<size_t>(decryptedSize);
|
2018-12-03 00:26:11 +01:00
|
|
|
if (!remainingSize) {
|
|
|
|
throw ParsingException("Decrypted buffer is empty.");
|
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
|
|
|
|
} else {
|
|
|
|
// use raw data directly if not encrypted
|
|
|
|
decryptedData.swap(rawData);
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// decompress
|
2017-05-01 03:25:30 +02:00
|
|
|
if (compressionUsed) {
|
2018-09-08 19:38:15 +02:00
|
|
|
if (remainingSize < 8) {
|
2016-08-29 15:42:07 +02:00
|
|
|
throw ParsingException("File is truncated (decompressed size expected).");
|
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
uLongf decompressedSize = ConversionUtilities::LE::toUInt64(decryptedData.data());
|
|
|
|
rawData.resize(decompressedSize);
|
|
|
|
switch (uncompress(
|
|
|
|
reinterpret_cast<Bytef *>(rawData.data()), &decompressedSize, reinterpret_cast<Bytef *>(decryptedData.data() + 8), remainingSize - 8)) {
|
2016-08-29 15:42:07 +02:00
|
|
|
case Z_MEM_ERROR:
|
|
|
|
throw ParsingException("Decompressing failed. The source buffer was too small.");
|
|
|
|
case Z_BUF_ERROR:
|
|
|
|
throw ParsingException("Decompressing failed. The destination buffer was too small.");
|
|
|
|
case Z_DATA_ERROR:
|
|
|
|
throw ParsingException("Decompressing failed. The input data was corrupted or incomplete.");
|
|
|
|
case Z_OK:
|
2018-09-08 19:38:15 +02:00
|
|
|
decryptedData.swap(rawData);
|
|
|
|
remainingSize = decompressedSize;
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
|
|
|
}
|
2018-12-03 00:26:11 +01:00
|
|
|
if (!remainingSize) {
|
|
|
|
throw ParsingException("Decompressed buffer is empty.");
|
|
|
|
}
|
2018-09-08 19:38:15 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// parse contents
|
2018-09-08 19:38:15 +02:00
|
|
|
stringstream decryptedStream(stringstream::in | stringstream::out | stringstream::binary);
|
2018-12-03 00:26:11 +01:00
|
|
|
decryptedStream.exceptions(ios_base::failbit | ios_base::badbit);
|
|
|
|
try {
|
2018-12-03 00:28:24 +01:00
|
|
|
#ifdef _LIBCPP_VERSION
|
|
|
|
decryptedStream.write(decryptedData.data(), static_cast<streamsize>(remainingSize));
|
|
|
|
#else
|
2018-12-03 00:26:11 +01:00
|
|
|
decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(), static_cast<streamsize>(remainingSize));
|
2018-12-03 00:28:24 +01:00
|
|
|
#endif
|
2018-12-21 01:12:53 +01:00
|
|
|
if (m_version >= 0x5u) {
|
2018-12-18 23:17:19 +01:00
|
|
|
BinaryReader reader(&decryptedStream);
|
|
|
|
const auto extendedHeaderSize = reader.readUInt16BE();
|
|
|
|
m_encryptedExtendedHeader = reader.readString(extendedHeaderSize);
|
|
|
|
} else {
|
|
|
|
m_encryptedExtendedHeader.clear();
|
2018-12-03 00:26:11 +01:00
|
|
|
}
|
|
|
|
m_rootEntry.reset(new NodeEntry(decryptedStream));
|
|
|
|
} catch (...) {
|
|
|
|
const char *const what = catchIoFailure();
|
|
|
|
if (decryptedStream.eof()) {
|
|
|
|
throw ParsingException("The file seems to be truncated.");
|
|
|
|
}
|
|
|
|
throw ParsingException(argsToString("An IO error occurred when reading internal buffer: ", what));
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-18 23:17:19 +01:00
|
|
|
/*!
|
|
|
|
* \brief Returns the minimum file version required to write the current instance with the specified \a options.
|
2018-12-21 01:12:53 +01:00
|
|
|
* \remarks This version will be used by save() and write() when passing the same \a options.
|
2018-12-18 23:17:19 +01:00
|
|
|
*/
|
|
|
|
uint32 PasswordFile::mininumVersion(PasswordFileSaveFlags options) const
|
|
|
|
{
|
|
|
|
if (options & PasswordFileSaveFlags::PasswordHashing) {
|
|
|
|
return 0x6U; // password hashing requires at least version 6
|
|
|
|
} else if (!m_encryptedExtendedHeader.empty()) {
|
|
|
|
return 0x5U; // encrypted extended header requires at least version 5
|
|
|
|
} else if (!m_extendedHeader.empty()) {
|
|
|
|
return 0x4U; // regular extended header requires at least version 4
|
|
|
|
}
|
|
|
|
return 0x3U; // lowest supported version by the serializer
|
|
|
|
}
|
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
/*!
|
2018-09-07 00:58:40 +02:00
|
|
|
* \brief Writes the current root entry to the file under path() replacing its previous contents.
|
2018-12-18 23:17:19 +01:00
|
|
|
* \param options Specify the features (like encryption and compression) to be used.
|
2016-08-29 15:42:07 +02:00
|
|
|
* \throws Throws ios_base::failure when an IO error occurs.
|
2018-06-09 18:34:46 +02:00
|
|
|
* \throws Throws runtime_error when no root entry is present or a compression error occurs.
|
|
|
|
* \throws Throws Io::CryptoException when an encryption error occurs.
|
2016-08-29 15:42:07 +02:00
|
|
|
*/
|
2018-12-18 23:17:19 +01:00
|
|
|
void PasswordFile::save(PasswordFileSaveFlags options)
|
2016-08-29 15:42:07 +02:00
|
|
|
{
|
2017-05-01 03:25:30 +02:00
|
|
|
if (!m_rootEntry) {
|
2016-08-29 15:42:07 +02:00
|
|
|
throw runtime_error("Root entry has not been created.");
|
|
|
|
}
|
2018-09-07 00:58:40 +02:00
|
|
|
|
|
|
|
// use already opened and writable file; otherwise re-open the file
|
|
|
|
if (m_file.good() && m_file.is_open() && (m_file.flags() & ios_base::out)) {
|
|
|
|
m_file.seekp(0);
|
|
|
|
} else {
|
2016-08-29 15:42:07 +02:00
|
|
|
m_file.close();
|
|
|
|
m_file.clear();
|
2018-09-07 00:58:40 +02:00
|
|
|
m_file.open(m_path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary);
|
|
|
|
}
|
|
|
|
|
2018-12-18 23:17:19 +01:00
|
|
|
write(options);
|
2018-09-07 00:58:40 +02:00
|
|
|
m_file.flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Writes the current root entry to the file which is assumed to be opened and writeable.
|
2018-12-18 23:17:19 +01:00
|
|
|
* \param options Specify the features (like encryption and compression) to be used.
|
2018-09-07 00:58:40 +02:00
|
|
|
* \throws Throws ios_base::failure when an IO error occurs.
|
|
|
|
* \throws Throws runtime_error when no root entry is present or a compression error occurs.
|
|
|
|
* \throws Throws Io::CryptoException when an encryption error occurs.
|
|
|
|
*/
|
2018-12-18 23:17:19 +01:00
|
|
|
void PasswordFile::write(PasswordFileSaveFlags options)
|
2018-09-07 00:58:40 +02:00
|
|
|
{
|
|
|
|
if (!m_rootEntry) {
|
|
|
|
throw runtime_error("Root entry has not been created.");
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
2018-09-07 00:58:40 +02:00
|
|
|
|
|
|
|
// write magic number
|
|
|
|
m_fwriter.writeUInt32LE(0x7770616DU);
|
|
|
|
|
2018-12-18 23:17:19 +01:00
|
|
|
// write version
|
|
|
|
const auto version = mininumVersion(options);
|
|
|
|
m_fwriter.writeUInt32LE(version);
|
|
|
|
|
|
|
|
// write flags
|
2016-08-29 15:42:07 +02:00
|
|
|
byte flags = 0x00;
|
2018-12-18 23:17:19 +01:00
|
|
|
if (options & PasswordFileSaveFlags::Encryption) {
|
2016-08-29 15:42:07 +02:00
|
|
|
flags |= 0x80 | 0x40;
|
|
|
|
}
|
2018-12-18 23:17:19 +01:00
|
|
|
if (options & PasswordFileSaveFlags::Compression) {
|
2016-08-29 15:42:07 +02:00
|
|
|
flags |= 0x20;
|
|
|
|
}
|
|
|
|
m_fwriter.writeByte(flags);
|
2018-09-07 01:14:21 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// write extened header
|
2018-12-18 23:17:19 +01:00
|
|
|
if (version >= 0x4U) {
|
|
|
|
if (m_extendedHeader.size() > numeric_limits<uint16>::max()) {
|
|
|
|
throw runtime_error("Extended header exceeds maximum size.");
|
|
|
|
}
|
2018-09-07 01:14:21 +02:00
|
|
|
m_fwriter.writeUInt16BE(static_cast<uint16>(m_extendedHeader.size()));
|
2016-08-29 15:42:07 +02:00
|
|
|
m_fwriter.writeString(m_extendedHeader);
|
|
|
|
}
|
2018-09-07 01:14:21 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// serialize root entry and descendants
|
|
|
|
stringstream buffstr(stringstream::in | stringstream::out | stringstream::binary);
|
|
|
|
buffstr.exceptions(ios_base::failbit | ios_base::badbit);
|
2018-09-07 01:14:21 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// write encrypted extened header
|
2018-12-18 23:17:19 +01:00
|
|
|
if (version >= 0x5U) {
|
|
|
|
if (m_encryptedExtendedHeader.size() > numeric_limits<uint16>::max()) {
|
|
|
|
throw runtime_error("Encrypted extended header exceeds maximum size.");
|
|
|
|
}
|
|
|
|
BinaryWriter buffstrWriter(&buffstr);
|
|
|
|
buffstrWriter.writeUInt16BE(static_cast<uint16>(m_encryptedExtendedHeader.size()));
|
|
|
|
buffstrWriter.writeString(m_encryptedExtendedHeader);
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
|
|
|
m_rootEntry->make(buffstr);
|
|
|
|
buffstr.seekp(0, ios_base::end);
|
2018-09-07 01:14:21 +02:00
|
|
|
auto size = static_cast<size_t>(buffstr.tellp());
|
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// write the data to a buffer
|
|
|
|
buffstr.seekg(0);
|
2018-09-08 19:38:15 +02:00
|
|
|
vector<char> decryptedData(size, 0);
|
|
|
|
buffstr.read(decryptedData.data(), static_cast<streamoff>(size));
|
|
|
|
vector<char> encryptedData;
|
2018-09-07 01:14:21 +02:00
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
// compress data
|
2018-12-18 23:17:19 +01:00
|
|
|
if (options & PasswordFileSaveFlags::Compression) {
|
2016-08-29 15:42:07 +02:00
|
|
|
uLongf compressedSize = compressBound(size);
|
2018-09-08 19:38:15 +02:00
|
|
|
encryptedData.resize(8 + compressedSize);
|
|
|
|
ConversionUtilities::LE::getBytes(static_cast<uint64>(size), encryptedData.data());
|
|
|
|
switch (
|
|
|
|
compress(reinterpret_cast<Bytef *>(encryptedData.data() + 8), &compressedSize, reinterpret_cast<Bytef *>(decryptedData.data()), size)) {
|
2016-08-29 15:42:07 +02:00
|
|
|
case Z_MEM_ERROR:
|
2018-06-09 18:34:46 +02:00
|
|
|
throw runtime_error("Compressing failed. The source buffer was too small.");
|
2016-08-29 15:42:07 +02:00
|
|
|
case Z_BUF_ERROR:
|
2018-06-09 18:34:46 +02:00
|
|
|
throw runtime_error("Compressing failed. The destination buffer was too small.");
|
2016-08-29 15:42:07 +02:00
|
|
|
case Z_OK:
|
2018-09-08 19:38:15 +02:00
|
|
|
encryptedData.swap(decryptedData); // decompression successful
|
2016-08-29 15:42:07 +02:00
|
|
|
size = 8 + compressedSize;
|
|
|
|
}
|
|
|
|
}
|
2018-09-07 01:14:21 +02:00
|
|
|
|
|
|
|
if (size > numeric_limits<int>::max()) {
|
|
|
|
throw CryptoException("size exceeds limit");
|
|
|
|
}
|
|
|
|
|
|
|
|
// write data without encryption
|
2018-12-18 23:17:19 +01:00
|
|
|
if (!(options & PasswordFileSaveFlags::Encryption)) {
|
2016-08-29 15:42:07 +02:00
|
|
|
// write data to file
|
2018-09-08 19:38:15 +02:00
|
|
|
m_file.write(decryptedData.data(), static_cast<streamsize>(size));
|
2018-09-07 01:14:21 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-12-18 23:17:19 +01:00
|
|
|
// prepare password
|
|
|
|
Util::OpenSsl::Sha256Sum password;
|
|
|
|
const uint32_t hashCount = (options & PasswordFileSaveFlags::PasswordHashing) ? Util::OpenSsl::generateRandomNumber(1, 100) : 0u;
|
|
|
|
if (hashCount) {
|
|
|
|
// hash password a few times
|
|
|
|
password = Util::OpenSsl::computeSha256Sum(reinterpret_cast<unsigned const char *>(m_password.data()), m_password.size());
|
|
|
|
for (uint32_t i = 1; i < hashCount; ++i) {
|
|
|
|
password = Util::OpenSsl::computeSha256Sum(password.data, Util::OpenSsl::Sha256Sum::size);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m_password.copy(reinterpret_cast<char *>(password.data), Util::OpenSsl::Sha256Sum::size);
|
|
|
|
}
|
|
|
|
|
2018-09-07 01:14:21 +02:00
|
|
|
// initiate ctx, encrypt data
|
|
|
|
EVP_CIPHER_CTX *ctx = nullptr;
|
|
|
|
unsigned char iv[aes256cbcIvSize];
|
|
|
|
int outlen1, outlen2;
|
2018-09-08 19:38:15 +02:00
|
|
|
encryptedData.resize(size + 32);
|
2018-09-07 01:14:21 +02:00
|
|
|
if (RAND_bytes(iv, aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) == nullptr
|
2018-12-18 23:17:19 +01:00
|
|
|
|| EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, password.data, iv) != 1
|
2018-09-08 19:38:15 +02:00
|
|
|
|| EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()), &outlen1,
|
|
|
|
reinterpret_cast<unsigned char *>(decryptedData.data()), static_cast<int>(size))
|
2018-09-07 01:14:21 +02:00
|
|
|
!= 1
|
2018-09-08 19:38:15 +02:00
|
|
|
|| EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()) + outlen1, &outlen2) != 1) {
|
2018-09-07 01:14:21 +02:00
|
|
|
// handle encryption error
|
|
|
|
if (ctx) {
|
|
|
|
EVP_CIPHER_CTX_free(ctx);
|
|
|
|
}
|
|
|
|
string msg;
|
|
|
|
auto errorCode = ERR_get_error();
|
|
|
|
while (errorCode) {
|
|
|
|
if (!msg.empty()) {
|
|
|
|
msg += "\n";
|
|
|
|
}
|
|
|
|
msg += ERR_error_string(errorCode, nullptr);
|
|
|
|
errorCode = ERR_get_error();
|
|
|
|
}
|
2018-12-21 17:31:24 +01:00
|
|
|
throw CryptoException(move(msg));
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
2018-09-07 01:14:21 +02:00
|
|
|
|
|
|
|
if (ctx) {
|
|
|
|
EVP_CIPHER_CTX_free(ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// write encrypted data to file
|
2018-12-18 23:17:19 +01:00
|
|
|
if (version >= 0x6U) {
|
|
|
|
m_fwriter.writeUInt32BE(hashCount);
|
|
|
|
}
|
2018-09-07 01:14:21 +02:00
|
|
|
m_file.write(reinterpret_cast<char *>(iv), aes256cbcIvSize);
|
2018-09-08 19:38:15 +02:00
|
|
|
m_file.write(encryptedData.data(), static_cast<streamsize>(outlen1 + outlen2));
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Removes the root element if one is present.
|
|
|
|
*/
|
|
|
|
void PasswordFile::clearEntries()
|
|
|
|
{
|
|
|
|
m_rootEntry.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Closes the file if opened. Removes path, password and entries and additional information.
|
|
|
|
*/
|
|
|
|
void PasswordFile::clear()
|
|
|
|
{
|
|
|
|
close();
|
|
|
|
clearPath();
|
|
|
|
clearPassword();
|
|
|
|
clearEntries();
|
2018-12-21 17:32:31 +01:00
|
|
|
m_openOptions = PasswordFileOpenFlags::None;
|
2016-08-29 15:42:07 +02:00
|
|
|
m_extendedHeader.clear();
|
|
|
|
m_encryptedExtendedHeader.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Writes the current root entry to a plain text file. No encryption is used.
|
|
|
|
* \param targetPath Specifies the path of the text file.
|
|
|
|
* \throws Throws ios_base::failure when an IO error occurs.
|
|
|
|
* \throws Throws runtime_error when no root entry is present.
|
|
|
|
*/
|
|
|
|
void PasswordFile::exportToTextfile(const string &targetPath) const
|
|
|
|
{
|
2017-05-01 03:25:30 +02:00
|
|
|
if (!m_rootEntry) {
|
2016-08-29 15:42:07 +02:00
|
|
|
throw runtime_error("Root entry has not been created.");
|
|
|
|
}
|
|
|
|
fstream output(targetPath.c_str(), ios_base::out);
|
2018-09-07 01:14:21 +02:00
|
|
|
const auto printIndention = [&output](int level) {
|
2017-05-01 03:25:30 +02:00
|
|
|
for (int i = 0; i < level; ++i) {
|
2016-08-29 15:42:07 +02:00
|
|
|
output << " ";
|
|
|
|
}
|
|
|
|
};
|
2017-05-01 03:25:30 +02:00
|
|
|
function<void(const Entry *entry, int level)> printNode;
|
2018-09-07 01:14:21 +02:00
|
|
|
printNode = [&output, &printNode, &printIndention](const Entry *entry, int level) {
|
|
|
|
printIndention(level);
|
2016-08-29 15:42:07 +02:00
|
|
|
output << " - " << entry->label() << endl;
|
2017-05-01 03:25:30 +02:00
|
|
|
switch (entry->type()) {
|
2016-08-29 15:42:07 +02:00
|
|
|
case EntryType::Node:
|
2017-05-01 03:25:30 +02:00
|
|
|
for (const Entry *child : static_cast<const NodeEntry *>(entry)->children()) {
|
2016-08-29 15:42:07 +02:00
|
|
|
printNode(child, level + 1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case EntryType::Account:
|
2017-05-01 03:25:30 +02:00
|
|
|
for (const Field &field : static_cast<const AccountEntry *>(entry)->fields()) {
|
2018-09-07 01:14:21 +02:00
|
|
|
printIndention(level);
|
2016-08-29 15:42:07 +02:00
|
|
|
output << " " << field.name();
|
2018-09-07 01:14:21 +02:00
|
|
|
for (auto i = field.name().length(); i < 15; ++i) {
|
2016-08-29 15:42:07 +02:00
|
|
|
output << ' ';
|
|
|
|
}
|
|
|
|
output << field.value() << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
printNode(m_rootEntry.get(), 0);
|
|
|
|
output.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Creates a backup of the file. Replaces an existent backup file.
|
|
|
|
* \throws Throws ios_base::failure when an IO error occurs.
|
|
|
|
*/
|
|
|
|
void PasswordFile::doBackup()
|
|
|
|
{
|
2017-05-01 03:25:30 +02:00
|
|
|
if (!isOpen()) {
|
2016-08-29 15:42:07 +02:00
|
|
|
open();
|
|
|
|
}
|
2018-09-07 01:14:21 +02:00
|
|
|
|
|
|
|
// skip if the current file is empty anyways
|
|
|
|
if (!size()) {
|
|
|
|
return;
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
2018-09-07 01:14:21 +02:00
|
|
|
|
|
|
|
m_file.seekg(0);
|
|
|
|
fstream backupFile(m_path + ".backup", ios::out | ios::trunc | ios::binary);
|
|
|
|
backupFile.exceptions(ios_base::failbit | ios_base::badbit);
|
|
|
|
backupFile << m_file.rdbuf();
|
|
|
|
backupFile.close();
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns an indication whether a root entry is present.
|
|
|
|
* \sa generateRootEntry()
|
|
|
|
* \sa rootEntry()
|
|
|
|
*/
|
|
|
|
bool PasswordFile::hasRootEntry() const
|
|
|
|
{
|
|
|
|
return m_rootEntry != nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the root entry if present or nullptr otherwise.
|
|
|
|
*/
|
|
|
|
const NodeEntry *PasswordFile::rootEntry() const
|
|
|
|
{
|
|
|
|
return m_rootEntry.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the root entry if present or nullptr otherwise.
|
|
|
|
*/
|
|
|
|
NodeEntry *PasswordFile::rootEntry()
|
|
|
|
{
|
|
|
|
return m_rootEntry.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Closes the file if currently opened.
|
|
|
|
*/
|
|
|
|
void PasswordFile::close()
|
|
|
|
{
|
2017-05-01 03:25:30 +02:00
|
|
|
if (m_file.is_open()) {
|
2016-08-29 15:42:07 +02:00
|
|
|
m_file.close();
|
|
|
|
}
|
|
|
|
m_file.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the current file path.
|
|
|
|
*/
|
|
|
|
const string &PasswordFile::path() const
|
|
|
|
{
|
|
|
|
return m_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2018-06-12 21:47:38 +02:00
|
|
|
* \brief Sets the current file path. Closes the file if currently opened.
|
2016-08-29 15:42:07 +02:00
|
|
|
*/
|
|
|
|
void PasswordFile::setPath(const string &value)
|
|
|
|
{
|
|
|
|
close();
|
|
|
|
m_path = value;
|
2018-06-12 21:47:38 +02:00
|
|
|
|
|
|
|
// support "file://" protocol
|
|
|
|
if (ConversionUtilities::startsWith(m_path, "file:")) {
|
|
|
|
m_path = m_path.substr(5);
|
|
|
|
}
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Clears the current path. Causes the file to be closed if currently opened.
|
|
|
|
*/
|
|
|
|
void PasswordFile::clearPath()
|
|
|
|
{
|
|
|
|
close();
|
|
|
|
m_path.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the current password. It will be used when loading or saving using encryption.
|
|
|
|
*/
|
2018-12-18 23:17:19 +01:00
|
|
|
const std::string &PasswordFile::password() const
|
2016-08-29 15:42:07 +02:00
|
|
|
{
|
|
|
|
return m_password;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2018-12-18 23:17:19 +01:00
|
|
|
* \brief Sets the current password. It will be used when loading an encrypted file or when saving using encryption.
|
2016-08-29 15:42:07 +02:00
|
|
|
*/
|
2018-12-18 23:17:19 +01:00
|
|
|
void PasswordFile::setPassword(const string &password)
|
2016-08-29 15:42:07 +02:00
|
|
|
{
|
2018-12-18 23:17:19 +01:00
|
|
|
m_password = password;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Sets the current password. It will be used when loading an encrypted file or when saving using encryption.
|
|
|
|
*/
|
|
|
|
void PasswordFile::setPassword(const char *password, const size_t passwordSize)
|
|
|
|
{
|
|
|
|
m_password.assign(password, passwordSize);
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Clears the current password.
|
|
|
|
*/
|
|
|
|
void PasswordFile::clearPassword()
|
|
|
|
{
|
2018-12-18 23:17:19 +01:00
|
|
|
m_password.clear();
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns an indication whether encryption is used if the file is open; returns always false otherwise.
|
|
|
|
*/
|
|
|
|
bool PasswordFile::isEncryptionUsed()
|
|
|
|
{
|
2017-05-01 03:25:30 +02:00
|
|
|
if (!isOpen()) {
|
2016-08-29 15:42:07 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
m_file.seekg(0);
|
2018-09-07 01:14:21 +02:00
|
|
|
|
2018-12-18 23:17:19 +01:00
|
|
|
// check magic number
|
2017-05-01 03:25:30 +02:00
|
|
|
if (m_freader.readUInt32LE() != 0x7770616DU) {
|
2016-08-29 15:42:07 +02:00
|
|
|
return false;
|
|
|
|
}
|
2018-09-07 01:14:21 +02:00
|
|
|
|
2018-12-18 23:17:19 +01:00
|
|
|
// check version
|
2018-09-07 01:14:21 +02:00
|
|
|
const auto version = m_freader.readUInt32LE();
|
2017-05-01 03:25:30 +02:00
|
|
|
if (version == 0x1U || version == 0x2U) {
|
2016-08-29 15:42:07 +02:00
|
|
|
return true;
|
2018-12-18 23:17:19 +01:00
|
|
|
} else if (version >= 0x3U) {
|
2016-08-29 15:42:07 +02:00
|
|
|
return m_freader.readByte() & 0x80;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns an indication whether the file is open.
|
|
|
|
*/
|
|
|
|
bool PasswordFile::isOpen() const
|
|
|
|
{
|
|
|
|
return m_file.is_open();
|
|
|
|
}
|
|
|
|
|
2018-12-18 23:17:19 +01:00
|
|
|
/*!
|
|
|
|
* \brief Returns the extended header.
|
|
|
|
*/
|
|
|
|
string &PasswordFile::extendedHeader()
|
|
|
|
{
|
|
|
|
return m_extendedHeader;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the extended header.
|
|
|
|
*/
|
|
|
|
const string &PasswordFile::extendedHeader() const
|
|
|
|
{
|
|
|
|
return m_extendedHeader;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the encrypted extended header.
|
|
|
|
*/
|
|
|
|
string &PasswordFile::encryptedExtendedHeader()
|
|
|
|
{
|
|
|
|
return m_encryptedExtendedHeader;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the encrypted extended header.
|
|
|
|
*/
|
|
|
|
const string &PasswordFile::encryptedExtendedHeader() const
|
|
|
|
{
|
|
|
|
return m_encryptedExtendedHeader;
|
|
|
|
}
|
|
|
|
|
2016-08-29 15:42:07 +02:00
|
|
|
/*!
|
2018-09-07 01:14:21 +02:00
|
|
|
* \brief Returns the size of the file if the file is open; otherwise returns zero.
|
2016-08-29 15:42:07 +02:00
|
|
|
*/
|
|
|
|
size_t PasswordFile::size()
|
|
|
|
{
|
2017-05-01 03:25:30 +02:00
|
|
|
if (!isOpen()) {
|
2016-08-29 15:42:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
m_file.seekg(0, ios::end);
|
2018-09-07 01:14:21 +02:00
|
|
|
return static_cast<size_t>(m_file.tellg());
|
2016-08-29 15:42:07 +02:00
|
|
|
}
|
2018-12-21 01:12:53 +01:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns a summary about the file (version, used features, statistics).
|
|
|
|
*/
|
|
|
|
string PasswordFile::summary(PasswordFileSaveFlags saveOptions) const
|
|
|
|
{
|
|
|
|
string result = "<table>";
|
|
|
|
if (!m_path.empty()) {
|
|
|
|
result += argsToString("<tr><td>Path:</td><td>", m_path, "</td></tr>");
|
|
|
|
}
|
|
|
|
result += argsToString("<tr><td>Version:</td><td>", m_version, "</td></tr>");
|
|
|
|
const auto minVersion = mininumVersion(saveOptions);
|
|
|
|
if (m_version != minVersion) {
|
|
|
|
result += argsToString("<tr><td></td><td>(on disk, after saving: ", minVersion, ")</td></tr>");
|
|
|
|
}
|
|
|
|
result += argsToString("<tr><td>Features:</td><td>", flagsToString(m_saveOptions), "</td></tr>");
|
|
|
|
if (m_saveOptions != saveOptions) {
|
|
|
|
result += argsToString("<tr><td></td><td>(on disk, after saving: ", flagsToString(saveOptions), ")</td></tr>");
|
|
|
|
}
|
|
|
|
const auto stats = m_rootEntry ? m_rootEntry->computeStatistics() : EntryStatistics();
|
|
|
|
result += argsToString("<tr><td>Number of categories:</td><td>", stats.nodeCount, "</td></tr><tr><td>Number of accounts:</td><td>",
|
|
|
|
stats.accountCount, "</td></tr><tr><td>Number of fields:</td><td>", stats.fieldCount, "</td></tr></table>");
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns a comma-separated string for the specified \a flags.
|
|
|
|
*/
|
|
|
|
string flagsToString(PasswordFileOpenFlags flags)
|
|
|
|
{
|
|
|
|
vector<string> options;
|
|
|
|
if (flags & PasswordFileOpenFlags::ReadOnly) {
|
|
|
|
options.emplace_back("read-only");
|
|
|
|
}
|
|
|
|
if (options.empty()) {
|
|
|
|
options.emplace_back("none");
|
|
|
|
}
|
|
|
|
return joinStrings(options, ", ");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns a comma-separated string for the specified \a flags.
|
|
|
|
*/
|
|
|
|
string flagsToString(PasswordFileSaveFlags flags)
|
|
|
|
{
|
|
|
|
vector<string> options;
|
|
|
|
options.reserve(3);
|
|
|
|
if (flags & PasswordFileSaveFlags::Encryption) {
|
|
|
|
options.emplace_back("encryption");
|
|
|
|
}
|
|
|
|
if (flags & PasswordFileSaveFlags::Compression) {
|
|
|
|
options.emplace_back("compression");
|
|
|
|
}
|
|
|
|
if (flags & PasswordFileSaveFlags::PasswordHashing) {
|
|
|
|
options.emplace_back("password hashing");
|
|
|
|
}
|
|
|
|
if (options.empty()) {
|
|
|
|
options.emplace_back("none");
|
|
|
|
}
|
|
|
|
return joinStrings(options, ", ");
|
|
|
|
}
|
|
|
|
|
2018-03-20 20:11:31 +01:00
|
|
|
} // namespace Io
|