Passwordfile library  3.1.4
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/io/catchiofailure.h>
7 
8 #include <openssl/conf.h>
9 #include <openssl/err.h>
10 #include <openssl/evp.h>
11 #include <openssl/rand.h>
12 
13 #include <zlib.h>
14 
15 #include <cstring>
16 #include <functional>
17 #include <memory>
18 #include <sstream>
19 #include <streambuf>
20 
21 using namespace std;
22 using namespace IoUtilities;
23 
24 namespace Io {
25 
26 const unsigned int aes256cbcIvSize = 16U;
27 
37 PasswordFile::PasswordFile()
38  : m_freader(BinaryReader(&m_file))
39  , m_fwriter(BinaryWriter(&m_file))
40 {
41  m_file.exceptions(ios_base::failbit | ios_base::badbit);
42  clearPassword();
43 }
44 
48 PasswordFile::PasswordFile(const string &path, const string &password)
49  : m_freader(BinaryReader(&m_file))
50  , m_fwriter(BinaryWriter(&m_file))
51 {
52  m_file.exceptions(ios_base::failbit | ios_base::badbit);
53  setPath(path);
54  setPassword(password);
55 }
56 
61  : m_path(other.m_path)
62  , m_freader(BinaryReader(&m_file))
63  , m_fwriter(BinaryWriter(&m_file))
64 {
65  m_file.exceptions(ios_base::failbit | ios_base::badbit);
66  setPath(other.path());
67  memcpy(m_password, other.m_password, 32);
68 }
69 
74 {
75  close();
76 }
77 
82 void PasswordFile::open(bool readOnly)
83 {
84  close();
85  if (m_path.empty()) {
86  throwIoFailure("Unable to open file because path is emtpy.");
87  }
88  m_file.open(m_path, readOnly ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary);
89  m_file.seekg(0, ios_base::end);
90  if (m_file.tellg() == 0) {
91  throwIoFailure("File is empty.");
92  } else {
93  m_file.seekg(0);
94  }
95 }
96 
101 {
102  if (!m_rootEntry) {
103  m_rootEntry.reset(new NodeEntry("accounts"));
104  }
105 }
106 
112 {
113  close();
114  if (m_path.empty()) {
115  throwIoFailure("Unable to create file because path is empty.");
116  }
117  m_file.open(m_path, fstream::out | fstream::trunc | fstream::binary);
118 }
119 
129 {
130  if (!m_file.is_open()) {
131  open();
132  }
133  m_file.seekg(0);
134  // check magic number
135  if (m_freader.readUInt32LE() != 0x7770616DU) {
136  throw ParsingException("Signature not present.");
137  }
138  // check version and flags (used in version 0x3 only)
139  uint32 version = m_freader.readUInt32LE();
140  if (version != 0x0U && version != 0x1U && version != 0x2U && version != 0x3U && version != 0x4U && version != 0x5U) {
141  throw ParsingException("Version is unknown.");
142  }
143  bool decrypterUsed;
144  bool ivUsed;
145  bool compressionUsed;
146  if (version == 0x3U) {
147  byte flags = m_freader.readByte();
148  decrypterUsed = flags & 0x80;
149  ivUsed = flags & 0x40;
150  compressionUsed = flags & 0x20;
151  } else {
152  decrypterUsed = version >= 0x1U;
153  ivUsed = version == 0x2U;
154  compressionUsed = false;
155  }
156  // skip extended header
157  // the extended header might be used in further versions to
158  // add additional information without breaking compatibility
159  if (version >= 0x4U) {
160  uint16 extendedHeaderSize = m_freader.readUInt16BE();
161  m_extendedHeader = m_freader.readString(extendedHeaderSize);
162  }
163  // get length
164  fstream::pos_type headerSize = m_file.tellg();
165  m_file.seekg(0, ios_base::end);
166  fstream::pos_type size = m_file.tellg();
167  m_file.seekg(headerSize, ios_base::beg);
168  size -= headerSize;
169  // read file
170  unsigned char iv[aes256cbcIvSize] = { 0 };
171  if (decrypterUsed && ivUsed) {
172  if (size < aes256cbcIvSize) {
173  throw ParsingException("Initiation vector not present.");
174  }
175  m_file.read(reinterpret_cast<char *>(iv), aes256cbcIvSize);
176  size -= aes256cbcIvSize;
177  }
178  if (size <= 0) {
179  throw ParsingException("No contents found.");
180  }
181  // decrypt contents
182  vector<char> rawbuff;
183  m_freader.read(rawbuff, size);
184  vector<char> decbuff;
185  if (decrypterUsed) {
186  // initiate ctx
187  EVP_CIPHER_CTX *ctx = nullptr;
188  decbuff.resize(size + static_cast<fstream::pos_type>(32));
189  int outlen1, outlen2;
190  if ((ctx = EVP_CIPHER_CTX_new()) == nullptr
191  || EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<unsigned const char *>(m_password), iv) != 1
192  || EVP_DecryptUpdate(
193  ctx, reinterpret_cast<unsigned char *>(decbuff.data()), &outlen1, reinterpret_cast<unsigned char *>(rawbuff.data()), size)
194  != 1
195  || EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(decbuff.data()) + outlen1, &outlen2) != 1) {
196  if (ctx) {
197  EVP_CIPHER_CTX_free(ctx);
198  }
199  string msg;
200  unsigned long errorCode = ERR_get_error();
201  while (errorCode != 0) {
202  if (!msg.empty()) {
203  msg += "\n";
204  }
205  msg += ERR_error_string(errorCode, 0);
206  errorCode = ERR_get_error();
207  }
208  throw CryptoException(msg);
209  } else { // decryption suceeded
210  if (ctx) {
211  EVP_CIPHER_CTX_free(ctx);
212  }
213  size = outlen1 + outlen2;
214  }
215  } else { // file is not crypted
216  decbuff.swap(rawbuff);
217  }
218  // decompress
219  if (compressionUsed) {
220  if (size < 8) {
221  throw ParsingException("File is truncated (decompressed size expected).");
222  }
223  uLongf decompressedSize = ConversionUtilities::LE::toUInt64(decbuff.data());
224  rawbuff.resize(decompressedSize);
225  switch (uncompress(reinterpret_cast<Bytef *>(rawbuff.data()), &decompressedSize, reinterpret_cast<Bytef *>(decbuff.data() + 8),
226  size - static_cast<fstream::pos_type>(8))) {
227  case Z_MEM_ERROR:
228  throw ParsingException("Decompressing failed. The source buffer was too small.");
229  case Z_BUF_ERROR:
230  throw ParsingException("Decompressing failed. The destination buffer was too small.");
231  case Z_DATA_ERROR:
232  throw ParsingException("Decompressing failed. The input data was corrupted or incomplete.");
233  case Z_OK:
234  decbuff.swap(rawbuff); // decompression successful
235  size = decompressedSize;
236  }
237  }
238  // parse contents
239  stringstream buffstr(stringstream::in | stringstream::out | stringstream::binary);
240  buffstr.write(decbuff.data(), static_cast<streamsize>(size));
241  decbuff.resize(0);
242  buffstr.seekg(0, ios_base::beg);
243  if (version >= 0x5u) {
244  uint16 extendedHeaderSize = m_freader.readUInt16BE();
245  m_encryptedExtendedHeader = m_freader.readString(extendedHeaderSize);
246  }
247  m_rootEntry.reset(new NodeEntry(buffstr));
248 }
249 
258 void PasswordFile::save(bool useEncryption, bool useCompression)
259 {
260  if (!m_rootEntry) {
261  throw runtime_error("Root entry has not been created.");
262  }
263  // open file
264  if (m_file.is_open()) {
265  m_file.close();
266  m_file.clear();
267  }
268  m_file.open(m_path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary);
269  // write header
270  m_fwriter.writeUInt32LE(0x7770616DU); // write magic number
271  // write version, extended header requires version 4, encrypted extended header required version 5
272  m_fwriter.writeUInt32LE(m_extendedHeader.empty() && m_encryptedExtendedHeader.empty() ? 0x3U : (m_encryptedExtendedHeader.empty() ? 0x4U : 0x5U));
273  byte flags = 0x00;
274  if (useEncryption) {
275  flags |= 0x80 | 0x40;
276  }
277  if (useCompression) {
278  flags |= 0x20;
279  }
280  m_fwriter.writeByte(flags);
281  // write extened header
282  if (!m_extendedHeader.empty()) {
283  m_fwriter.writeUInt16BE(m_extendedHeader.size());
284  m_fwriter.writeString(m_extendedHeader);
285  }
286  // serialize root entry and descendants
287  stringstream buffstr(stringstream::in | stringstream::out | stringstream::binary);
288  buffstr.exceptions(ios_base::failbit | ios_base::badbit);
289  // write encrypted extened header
290  if (!m_encryptedExtendedHeader.empty()) {
291  m_fwriter.writeUInt16BE(m_encryptedExtendedHeader.size());
292  m_fwriter.writeString(m_encryptedExtendedHeader);
293  }
294  m_rootEntry->make(buffstr);
295  buffstr.seekp(0, ios_base::end);
296  stringstream::pos_type size = buffstr.tellp();
297  // write the data to a buffer
298  buffstr.seekg(0);
299  vector<char> decbuff(size, 0);
300  buffstr.read(decbuff.data(), size);
301  vector<char> encbuff;
302  // compress data
303  if (useCompression) {
304  uLongf compressedSize = compressBound(size);
305  encbuff.resize(8 + compressedSize);
306  ConversionUtilities::LE::getBytes(static_cast<uint64>(size), encbuff.data());
307  switch (compress(reinterpret_cast<Bytef *>(encbuff.data() + 8), &compressedSize, reinterpret_cast<Bytef *>(decbuff.data()), size)) {
308  case Z_MEM_ERROR:
309  throw runtime_error("Decompressing failed. The source buffer was too small.");
310  case Z_BUF_ERROR:
311  throw runtime_error("Decompressing failed. The destination buffer was too small.");
312  case Z_OK:
313  encbuff.swap(decbuff); // decompression successful
314  size = 8 + compressedSize;
315  }
316  }
317  // encrypt data
318  if (useEncryption) {
319  // initiate ctx
320  EVP_CIPHER_CTX *ctx = nullptr;
321  unsigned char iv[aes256cbcIvSize];
322  int outlen1, outlen2;
323  encbuff.resize(size + static_cast<fstream::pos_type>(32));
324  if (RAND_bytes(iv, aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) == nullptr
325  || EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<unsigned const char *>(m_password), iv) != 1
326  || EVP_EncryptUpdate(
327  ctx, reinterpret_cast<unsigned char *>(encbuff.data()), &outlen1, reinterpret_cast<unsigned char *>(decbuff.data()), size)
328  != 1
329  || EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(encbuff.data()) + outlen1, &outlen2) != 1) {
330  if (ctx) {
331  EVP_CIPHER_CTX_free(ctx);
332  }
333  string msg;
334  unsigned long errorCode = ERR_get_error();
335  while (errorCode != 0) {
336  if (!msg.empty()) {
337  msg += "\n";
338  }
339  msg += ERR_error_string(errorCode, 0);
340  errorCode = ERR_get_error();
341  }
342  throw CryptoException(msg);
343  } else { // decryption succeeded
344  if (ctx) {
345  EVP_CIPHER_CTX_free(ctx);
346  }
347  // write encrypted data to file
348  m_file.write(reinterpret_cast<char *>(iv), aes256cbcIvSize);
349  m_file.write(encbuff.data(), static_cast<streamsize>(outlen1 + outlen2));
350  }
351  } else {
352  // write data to file
353  m_file.write(decbuff.data(), static_cast<streamsize>(size));
354  }
355  m_file.flush();
356 }
357 
362 {
363  m_rootEntry.reset();
364 }
365 
370 {
371  close();
372  clearPath();
373  clearPassword();
374  clearEntries();
375  m_extendedHeader.clear();
376  m_encryptedExtendedHeader.clear();
377 }
378 
385 void PasswordFile::exportToTextfile(const string &targetPath) const
386 {
387  if (!m_rootEntry) {
388  throw runtime_error("Root entry has not been created.");
389  }
390  fstream output(targetPath.c_str(), ios_base::out);
391  function<void(int level)> indention = [&output](int level) {
392  for (int i = 0; i < level; ++i) {
393  output << " ";
394  }
395  };
396  function<void(const Entry *entry, int level)> printNode;
397  printNode = [&output, &printNode, &indention](const Entry *entry, int level) {
398  indention(level);
399  output << " - " << entry->label() << endl;
400  switch (entry->type()) {
401  case EntryType::Node:
402  for (const Entry *child : static_cast<const NodeEntry *>(entry)->children()) {
403  printNode(child, level + 1);
404  }
405  break;
406  case EntryType::Account:
407  for (const Field &field : static_cast<const AccountEntry *>(entry)->fields()) {
408  indention(level);
409  output << " " << field.name();
410  for (int i = field.name().length(); i < 15; ++i) {
411  output << ' ';
412  }
413  output << field.value() << endl;
414  }
415  }
416  };
417  printNode(m_rootEntry.get(), 0);
418  output.close();
419 }
420 
426 {
427  if (!isOpen()) {
428  open();
429  }
430  m_file.seekg(0, ios_base::end);
431  if (m_file.tellg()) {
432  m_file.seekg(0);
433  fstream backupFile(m_path + ".backup", ios::out | ios::trunc | ios::binary);
434  backupFile.exceptions(ios_base::failbit | ios_base::badbit);
435  backupFile << m_file.rdbuf();
436  backupFile.close();
437  } else {
438  // the current file is empty anyways
439  }
440 }
441 
448 {
449  return m_rootEntry != nullptr;
450 }
451 
456 {
457  return m_rootEntry.get();
458 }
459 
464 {
465  return m_rootEntry.get();
466 }
467 
472 {
473  if (m_file.is_open()) {
474  m_file.close();
475  }
476  m_file.clear();
477 }
478 
482 const string &PasswordFile::path() const
483 {
484  return m_path;
485 }
486 
490 void PasswordFile::setPath(const string &value)
491 {
492  close();
493  m_path = value;
494 }
495 
500 {
501  close();
502  m_path.clear();
503 }
504 
508 const char *PasswordFile::password() const
509 {
510  return m_password;
511 }
512 
516 void PasswordFile::setPassword(const string &value)
517 {
518  clearPassword();
519  value.copy(m_password, 32, 0);
520 }
521 
526 {
527  memset(m_password, 0, 32);
528 }
529 
534 {
535  if (!isOpen()) {
536  return false;
537  }
538  m_file.seekg(0);
539  //check magic number
540  if (m_freader.readUInt32LE() != 0x7770616DU) {
541  return false;
542  }
543  //check version
544  uint32 version = m_freader.readUInt32LE();
545  if (version == 0x1U || version == 0x2U) {
546  return true;
547  } else if (version == 0x3U) {
548  return m_freader.readByte() & 0x80;
549  } else {
550  return false;
551  }
552 }
553 
558 {
559  return m_file.is_open();
560 }
561 
566 {
567  if (!isOpen()) {
568  return 0;
569  }
570  m_file.seekg(0, ios::end);
571  return m_file.tellg();
572 }
573 }
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:95
The PasswordFile class holds account information in the form of Entry and Field instances and provide...
Definition: passwordfile.h:18
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 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.
size_t size()
Returns the size of the file if the file is open; returns always zero otherwise.
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 save(bool useEncryption=true, bool useCompression=true)
Writes the current root entry to the file.
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