Passwordfile library  3.2.0
C++ library to read/write passwords from/to encrypted files
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 <c++utilities/conversion/stringconversion.h>
7 #include <c++utilities/io/catchiofailure.h>
8 
9 #include <openssl/conf.h>
10 #include <openssl/err.h>
11 #include <openssl/evp.h>
12 #include <openssl/rand.h>
13 
14 #include <zlib.h>
15 
16 #include <cstring>
17 #include <functional>
18 #include <limits>
19 #include <memory>
20 #include <sstream>
21 #include <streambuf>
22 
23 using namespace std;
24 using namespace IoUtilities;
25 
26 namespace Io {
27 
28 const unsigned int aes256cbcIvSize = 16U;
29 
39 PasswordFile::PasswordFile()
40  : m_freader(BinaryReader(&m_file))
41  , m_fwriter(BinaryWriter(&m_file))
42 {
43  m_file.exceptions(ios_base::failbit | ios_base::badbit);
44  clearPassword();
45 }
46 
50 PasswordFile::PasswordFile(const string &path, const string &password)
51  : m_freader(BinaryReader(&m_file))
52  , m_fwriter(BinaryWriter(&m_file))
53 {
54  m_file.exceptions(ios_base::failbit | ios_base::badbit);
55  setPath(path);
57 }
58 
63  : m_path(other.m_path)
64  , m_rootEntry(other.m_rootEntry ? make_unique<NodeEntry>(*other.m_rootEntry) : nullptr)
65  , m_extendedHeader(other.m_extendedHeader)
66  , m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
67  , m_freader(BinaryReader(&m_file))
68  , m_fwriter(BinaryWriter(&m_file))
69 {
70  m_file.exceptions(ios_base::failbit | ios_base::badbit);
71  memcpy(m_password, other.m_password, 32);
72 }
73 
78  : m_path(move(other.m_path))
79  , m_rootEntry(move(other.m_rootEntry))
80  , m_extendedHeader(move(other.m_extendedHeader))
81  , m_encryptedExtendedHeader(move(other.m_encryptedExtendedHeader))
82  , m_file(move(other.m_file))
83  , m_freader(BinaryReader(&m_file))
84  , m_fwriter(BinaryWriter(&m_file))
85 {
86  memcpy(m_password, other.m_password, 32);
87 }
88 
93 {
94 }
95 
100 void PasswordFile::open(bool readOnly)
101 {
102  close();
103  if (m_path.empty()) {
104  throwIoFailure("Unable to open file because path is emtpy.");
105  }
106  m_file.open(m_path, readOnly ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary);
107  opened();
108 }
109 
116 {
117  m_file.seekg(0, ios_base::end);
118  if (m_file.tellg() == 0) {
119  throwIoFailure("File is empty.");
120  } else {
121  m_file.seekg(0);
122  }
123 }
124 
129 {
130  if (!m_rootEntry) {
131  m_rootEntry.reset(new NodeEntry("accounts"));
132  }
133 }
134 
140 {
141  close();
142  if (m_path.empty()) {
143  throwIoFailure("Unable to create file because path is empty.");
144  }
145  m_file.open(m_path, fstream::out | fstream::trunc | fstream::binary);
146 }
147 
157 {
158  if (!m_file.is_open()) {
159  open();
160  }
161  m_file.seekg(0);
162 
163  // check magic number
164  if (m_freader.readUInt32LE() != 0x7770616DU) {
165  throw ParsingException("Signature not present.");
166  }
167 
168  // check version and flags (used in version 0x3 only)
169  const auto version = m_freader.readUInt32LE();
170  if (version != 0x0U && version != 0x1U && version != 0x2U && version != 0x3U && version != 0x4U && version != 0x5U) {
171  throw ParsingException("Version is unknown.");
172  }
173  bool decrypterUsed, ivUsed, compressionUsed;
174  if (version == 0x3U) {
175  const auto flags = m_freader.readByte();
176  decrypterUsed = flags & 0x80;
177  ivUsed = flags & 0x40;
178  compressionUsed = flags & 0x20;
179  } else {
180  decrypterUsed = version >= 0x1U;
181  ivUsed = version == 0x2U;
182  compressionUsed = false;
183  }
184 
185  // skip extended header
186  // (the extended header might be used in further versions to
187  // add additional information without breaking compatibility)
188  if (version >= 0x4U) {
189  uint16 extendedHeaderSize = m_freader.readUInt16BE();
190  m_extendedHeader = m_freader.readString(extendedHeaderSize);
191  }
192 
193  // get length
194  const auto headerSize = static_cast<size_t>(m_file.tellg());
195  m_file.seekg(0, ios_base::end);
196  auto remainingSize = static_cast<size_t>(m_file.tellg()) - headerSize;
197  m_file.seekg(static_cast<streamoff>(headerSize), ios_base::beg);
198 
199  // read file
200  unsigned char iv[aes256cbcIvSize] = { 0 };
201  if (decrypterUsed && ivUsed) {
202  if (remainingSize < aes256cbcIvSize) {
203  throw ParsingException("Initiation vector is truncated.");
204  }
205  m_file.read(reinterpret_cast<char *>(iv), aes256cbcIvSize);
206  remainingSize -= aes256cbcIvSize;
207  }
208  if (!remainingSize) {
209  throw ParsingException("No contents found.");
210  }
211 
212  // decrypt contents
213  vector<char> rawData;
214  m_freader.read(rawData, static_cast<streamoff>(remainingSize));
215  vector<char> decryptedData;
216  if (decrypterUsed) {
217  if (remainingSize > numeric_limits<int>::max()) {
218  throw CryptoException("Size exceeds limit.");
219  }
220 
221  // initiate ctx, decrypt data
222  EVP_CIPHER_CTX *ctx = nullptr;
223  decryptedData.resize(remainingSize + 32);
224  int outlen1, outlen2;
225  if ((ctx = EVP_CIPHER_CTX_new()) == nullptr
226  || EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<unsigned const char *>(m_password), iv) != 1
227  || EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()), &outlen1,
228  reinterpret_cast<unsigned char *>(rawData.data()), static_cast<int>(remainingSize))
229  != 1
230  || EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()) + outlen1, &outlen2) != 1) {
231  // handle decryption error
232  if (ctx) {
233  EVP_CIPHER_CTX_free(ctx);
234  }
235  string msg;
236  auto errorCode = ERR_get_error();
237  while (errorCode) {
238  if (!msg.empty()) {
239  msg += "\n";
240  }
241  msg += ERR_error_string(errorCode, nullptr);
242  errorCode = ERR_get_error();
243  }
244  throw CryptoException(msg);
245  }
246 
247  if (ctx) {
248  EVP_CIPHER_CTX_free(ctx);
249  }
250  const auto decryptedSize = outlen1 + outlen2;
251  if (decryptedSize < 0) {
252  throw CryptoException("Decrypted size is negative.");
253  }
254  remainingSize = static_cast<size_t>(decryptedSize);
255 
256  } else {
257  // use raw data directly if not encrypted
258  decryptedData.swap(rawData);
259  }
260 
261  // decompress
262  if (compressionUsed) {
263  if (remainingSize < 8) {
264  throw ParsingException("File is truncated (decompressed size expected).");
265  }
266  uLongf decompressedSize = ConversionUtilities::LE::toUInt64(decryptedData.data());
267  rawData.resize(decompressedSize);
268  switch (uncompress(
269  reinterpret_cast<Bytef *>(rawData.data()), &decompressedSize, reinterpret_cast<Bytef *>(decryptedData.data() + 8), remainingSize - 8)) {
270  case Z_MEM_ERROR:
271  throw ParsingException("Decompressing failed. The source buffer was too small.");
272  case Z_BUF_ERROR:
273  throw ParsingException("Decompressing failed. The destination buffer was too small.");
274  case Z_DATA_ERROR:
275  throw ParsingException("Decompressing failed. The input data was corrupted or incomplete.");
276  case Z_OK:
277  decryptedData.swap(rawData);
278  remainingSize = decompressedSize;
279  }
280  }
281 
282  // parse contents
283  stringstream decryptedStream(stringstream::in | stringstream::out | stringstream::binary);
284  decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(), static_cast<streamsize>(remainingSize));
285  if (version >= 0x5u) {
286  const auto extendedHeaderSize = m_freader.readUInt16BE();
287  m_encryptedExtendedHeader = m_freader.readString(extendedHeaderSize);
288  }
289  m_rootEntry.reset(new NodeEntry(decryptedStream));
290 }
291 
300 void PasswordFile::save(bool useEncryption, bool useCompression)
301 {
302  if (!m_rootEntry) {
303  throw runtime_error("Root entry has not been created.");
304  }
305 
306  // use already opened and writable file; otherwise re-open the file
307  if (m_file.good() && m_file.is_open() && (m_file.flags() & ios_base::out)) {
308  m_file.seekp(0);
309  } else {
310  m_file.close();
311  m_file.clear();
312  m_file.open(m_path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary);
313  }
314 
315  write(useEncryption, useCompression);
316  m_file.flush();
317 }
318 
327 void PasswordFile::write(bool useEncryption, bool useCompression)
328 {
329  if (!m_rootEntry) {
330  throw runtime_error("Root entry has not been created.");
331  }
332 
333  // write magic number
334  m_fwriter.writeUInt32LE(0x7770616DU);
335 
336  // write version, extended header requires version 4, encrypted extended header required version 5
337  m_fwriter.writeUInt32LE(m_extendedHeader.empty() && m_encryptedExtendedHeader.empty() ? 0x3U : (m_encryptedExtendedHeader.empty() ? 0x4U : 0x5U));
338  byte flags = 0x00;
339  if (useEncryption) {
340  flags |= 0x80 | 0x40;
341  }
342  if (useCompression) {
343  flags |= 0x20;
344  }
345  m_fwriter.writeByte(flags);
346 
347  // write extened header
348  if (!m_extendedHeader.empty()) {
349  m_fwriter.writeUInt16BE(static_cast<uint16>(m_extendedHeader.size()));
350  m_fwriter.writeString(m_extendedHeader);
351  }
352 
353  // serialize root entry and descendants
354  stringstream buffstr(stringstream::in | stringstream::out | stringstream::binary);
355  buffstr.exceptions(ios_base::failbit | ios_base::badbit);
356 
357  // write encrypted extened header
358  if (!m_encryptedExtendedHeader.empty()) {
359  m_fwriter.writeUInt16BE(static_cast<uint16>(m_encryptedExtendedHeader.size()));
360  m_fwriter.writeString(m_encryptedExtendedHeader);
361  }
362  m_rootEntry->make(buffstr);
363  buffstr.seekp(0, ios_base::end);
364  auto size = static_cast<size_t>(buffstr.tellp());
365 
366  // write the data to a buffer
367  buffstr.seekg(0);
368  vector<char> decryptedData(size, 0);
369  buffstr.read(decryptedData.data(), static_cast<streamoff>(size));
370  vector<char> encryptedData;
371 
372  // compress data
373  if (useCompression) {
374  uLongf compressedSize = compressBound(size);
375  encryptedData.resize(8 + compressedSize);
376  ConversionUtilities::LE::getBytes(static_cast<uint64>(size), encryptedData.data());
377  switch (
378  compress(reinterpret_cast<Bytef *>(encryptedData.data() + 8), &compressedSize, reinterpret_cast<Bytef *>(decryptedData.data()), size)) {
379  case Z_MEM_ERROR:
380  throw runtime_error("Compressing failed. The source buffer was too small.");
381  case Z_BUF_ERROR:
382  throw runtime_error("Compressing failed. The destination buffer was too small.");
383  case Z_OK:
384  encryptedData.swap(decryptedData); // decompression successful
385  size = 8 + compressedSize;
386  }
387  }
388 
389  if (size > numeric_limits<int>::max()) {
390  throw CryptoException("size exceeds limit");
391  }
392 
393  // write data without encryption
394  if (!useEncryption) {
395  // write data to file
396  m_file.write(decryptedData.data(), static_cast<streamsize>(size));
397  return;
398  }
399 
400  // initiate ctx, encrypt data
401  EVP_CIPHER_CTX *ctx = nullptr;
402  unsigned char iv[aes256cbcIvSize];
403  int outlen1, outlen2;
404  encryptedData.resize(size + 32);
405  if (RAND_bytes(iv, aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) == nullptr
406  || EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<unsigned const char *>(m_password), iv) != 1
407  || EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()), &outlen1,
408  reinterpret_cast<unsigned char *>(decryptedData.data()), static_cast<int>(size))
409  != 1
410  || EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()) + outlen1, &outlen2) != 1) {
411  // handle encryption error
412  if (ctx) {
413  EVP_CIPHER_CTX_free(ctx);
414  }
415  string msg;
416  auto errorCode = ERR_get_error();
417  while (errorCode) {
418  if (!msg.empty()) {
419  msg += "\n";
420  }
421  msg += ERR_error_string(errorCode, nullptr);
422  errorCode = ERR_get_error();
423  }
424  throw CryptoException(msg);
425  }
426 
427  if (ctx) {
428  EVP_CIPHER_CTX_free(ctx);
429  }
430 
431  // write encrypted data to file
432  m_file.write(reinterpret_cast<char *>(iv), aes256cbcIvSize);
433  m_file.write(encryptedData.data(), static_cast<streamsize>(outlen1 + outlen2));
434 }
435 
440 {
441  m_rootEntry.reset();
442 }
443 
448 {
449  close();
450  clearPath();
451  clearPassword();
452  clearEntries();
453  m_extendedHeader.clear();
454  m_encryptedExtendedHeader.clear();
455 }
456 
463 void PasswordFile::exportToTextfile(const string &targetPath) const
464 {
465  if (!m_rootEntry) {
466  throw runtime_error("Root entry has not been created.");
467  }
468  fstream output(targetPath.c_str(), ios_base::out);
469  const auto printIndention = [&output](int level) {
470  for (int i = 0; i < level; ++i) {
471  output << " ";
472  }
473  };
474  function<void(const Entry *entry, int level)> printNode;
475  printNode = [&output, &printNode, &printIndention](const Entry *entry, int level) {
476  printIndention(level);
477  output << " - " << entry->label() << endl;
478  switch (entry->type()) {
479  case EntryType::Node:
480  for (const Entry *child : static_cast<const NodeEntry *>(entry)->children()) {
481  printNode(child, level + 1);
482  }
483  break;
484  case EntryType::Account:
485  for (const Field &field : static_cast<const AccountEntry *>(entry)->fields()) {
486  printIndention(level);
487  output << " " << field.name();
488  for (auto i = field.name().length(); i < 15; ++i) {
489  output << ' ';
490  }
491  output << field.value() << endl;
492  }
493  }
494  };
495  printNode(m_rootEntry.get(), 0);
496  output.close();
497 }
498 
504 {
505  if (!isOpen()) {
506  open();
507  }
508 
509  // skip if the current file is empty anyways
510  if (!size()) {
511  return;
512  }
513 
514  m_file.seekg(0);
515  fstream backupFile(m_path + ".backup", ios::out | ios::trunc | ios::binary);
516  backupFile.exceptions(ios_base::failbit | ios_base::badbit);
517  backupFile << m_file.rdbuf();
518  backupFile.close();
519 }
520 
527 {
528  return m_rootEntry != nullptr;
529 }
530 
535 {
536  return m_rootEntry.get();
537 }
538 
543 {
544  return m_rootEntry.get();
545 }
546 
551 {
552  if (m_file.is_open()) {
553  m_file.close();
554  }
555  m_file.clear();
556 }
557 
561 const string &PasswordFile::path() const
562 {
563  return m_path;
564 }
565 
569 void PasswordFile::setPath(const string &value)
570 {
571  close();
572  m_path = value;
573 
574  // support "file://" protocol
575  if (ConversionUtilities::startsWith(m_path, "file:")) {
576  m_path = m_path.substr(5);
577  }
578 }
579 
584 {
585  close();
586  m_path.clear();
587 }
588 
592 const char *PasswordFile::password() const
593 {
594  return m_password;
595 }
596 
600 void PasswordFile::setPassword(const string &value)
601 {
602  clearPassword();
603  value.copy(m_password, 32, 0);
604 }
605 
610 {
611  memset(m_password, 0, 32);
612 }
613 
618 {
619  if (!isOpen()) {
620  return false;
621  }
622  m_file.seekg(0);
623 
624  //check magic number
625  if (m_freader.readUInt32LE() != 0x7770616DU) {
626  return false;
627  }
628 
629  //check version
630  const auto version = m_freader.readUInt32LE();
631  if (version == 0x1U || version == 0x2U) {
632  return true;
633  } else if (version == 0x3U) {
634  return m_freader.readByte() & 0x80;
635  } else {
636  return false;
637  }
638 }
639 
644 {
645  return m_file.is_open();
646 }
647 
652 {
653  if (!isOpen()) {
654  return 0;
655  }
656  m_file.seekg(0, ios::end);
657  return static_cast<size_t>(m_file.tellg());
658 }
659 } // namespace Io
const std::string & path() const
Returns the current file path.
PasswordFile()
Constructs a new password file.
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.
Definition: entry.h:96
The PasswordFile class holds account information in the form of Entry and Field instances and provide...
Definition: passwordfile.h:19
void load()
Reads the contents of the file.
void open(bool readOnly=false)
Opens the file.
STL namespace.
void close()
Closes the file if currently opened.
void write(bool useEncryption=true, bool useCompression=true)
Writes the current root entry to the file which is assumed to be opened and writeable.
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.
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...
Definition: field.h:15
void clearPath()
Clears the current path.
bool isEncryptionUsed()
Returns an indication whether encryption is used if the file is open; returns always false otherwise...
const unsigned int aes256cbcIvSize
~PasswordFile()
Closes the file if still opened and destroys the instance.
void setPath(const std::string &value)
Sets the current file path.
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.
const char * password() const
Returns the current password.
The exception that is thrown when an encryption/decryption error occurs.
void setPassword(const std::string &value)
Sets the current password.
void opened()
Handles the file being opened.
void save(bool useEncryption=true, bool useCompression=true)
Writes the current root entry to the file under path() replacing its previous contents.
The exception that is thrown when a parsing error occurs.
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...
Definition: entry.h:25