1 /*
2  * Copyright (C) 2018 Rafael Ostertag
3  *
4  * This file is part of YAPET.
5  *
6  * YAPET is free software: you can redistribute it and/or modify it under the
7  * terms of the GNU General Public License as published by the Free Software
8  * Foundation, either version 3 of the License, or (at your option) any later
9  * version.
10  *
11  * YAPET is distributed in the hope that it will be useful, but WITHOUT ANY
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * YAPET.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * Additional permission under GNU GPL version 3 section 7
20  *
21  * If you modify this program, or any covered work, by linking or combining it
22  * with the OpenSSL project's OpenSSL library (or a modified version of that
23  * library), containing parts covered by the terms of the OpenSSL or SSLeay
24  * licenses, Rafael Ostertag grants you additional permission to convey the
25  * resulting work.  Corresponding Source for a non-source form of such a
26  * combination shall include the source code for the parts of OpenSSL used as
27  * well as that of the covered work.
28  */
29 
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33 
34 #include <algorithm>
35 
36 #include "cryptoerror.hh"
37 #include "file.h"
38 #include "fileutils.hh"
39 #include "intl.h"
40 #include "logger.hh"
41 #include "yapeterror.hh"
42 
43 using namespace YAPET;
44 using namespace yapet;
45 
readHeader()46 Header10 File::readHeader() {
47     auto encryptedSerializedHeader{_yapetFile->readHeader()};
48     auto serializedHeader{_crypto->decrypt(encryptedSerializedHeader)};
49     return Header10{serializedHeader};
50 }
51 
initializeEmptyFile()52 void File::initializeEmptyFile() {
53     Header10 header{time(0)};
54 
55     _yapetFile->writeIdentifier();
56     _yapetFile->writeUnencryptedMetaData(
57         _crypto->getKey()->keyingParameters().serialize());
58 
59     auto encryptedSerializedHeader = _crypto->encrypt(header.serialize());
60 
61     _yapetFile->writeHeader(encryptedSerializedHeader);
62 }
63 
validateExistingFile()64 void File::validateExistingFile() {
65     auto encryptedSerializedHeader = _yapetFile->readHeader();
66     yapet::SecureArray serializedHeader;
67     try {
68         serializedHeader = _crypto->decrypt(encryptedSerializedHeader);
69     } catch (EncryptionError& e) {
70         LOG_MESSAGE(std::string{__func__} + ": invalid password");
71         // most likely caused by invalid password
72         throw InvalidPasswordError{_("Invalid password")};
73     }
74 
75     Header10 header;
76     try {
77         header = Header10{serializedHeader};
78     } catch (ControlStringMismatch& e) {
79         LOG_MESSAGE(std::string{__func__} + ": invalid password");
80     }
81 
82     // If we read an invalid header version, intToHeaderVersion() will throw an
83     // exception
84     intToHeaderVersion(header.version());
85 }
86 
notModifiedOrThrow()87 void File::notModifiedOrThrow() {
88     auto currentFileModificationTime{
89         yapet::getModificationTime(_yapetFile->filename())};
90     if (currentFileModificationTime != _fileModificationTime) {
91         throw RetryableError{_("File has been externally modified")};
92     }
93 }
94 
File(std::shared_ptr<yapet::AbstractCryptoFactory> abstractCryptoFactory,const std::string & filename,bool create,bool secure)95 File::File(std::shared_ptr<yapet::AbstractCryptoFactory> abstractCryptoFactory,
96            const std::string& filename, bool create, bool secure)
97     : _fileModificationTime{0},
98       _abstractCryptoFactory{abstractCryptoFactory},
99       _yapetFile{abstractCryptoFactory->file(filename, create, secure)},
100       _crypto{abstractCryptoFactory->crypto()} {
101     _yapetFile->open();
102     if (getFileSize(filename) == 0) {
103         initializeEmptyFile();
104     } else {
105         validateExistingFile();
106     }
107 
108     _fileModificationTime = getModificationTime(_yapetFile->filename());
109 }
110 
~File()111 File::~File() {}
112 
save(const std::list<PasswordListItem> & records,bool forcewrite)113 void File::save(const std::list<PasswordListItem>& records, bool forcewrite) {
114     if (!forcewrite) {
115         notModifiedOrThrow();
116     }
117 
118     std::list<SecureArray> encryptedPasswordRecords{};
119     std::for_each(records.begin(), records.end(),
120                   [&encryptedPasswordRecords](const PasswordListItem& i) {
121                       encryptedPasswordRecords.push_back(i.encryptedRecord());
122                   });
123 
124     _yapetFile->writePasswordRecords(encryptedPasswordRecords);
125     _fileModificationTime = yapet::getModificationTime(_yapetFile->filename());
126     LOG_MESSAGE("Save yapet file");
127 }
128 
read()129 std::list<PasswordListItem> File::read() {
130     auto encryptedPasswordRecords{_yapetFile->readPasswordRecords()};
131 
132     std::list<SecureArray>::iterator it = encryptedPasswordRecords.begin();
133     std::list<PasswordListItem> result;
134     while (it != encryptedPasswordRecords.end()) {
135         auto decryptedSerializedPasswordRecord{_crypto->decrypt(*it)};
136         PasswordRecord passwordRecord{decryptedSerializedPasswordRecord};
137 
138         result.push_back(PasswordListItem{
139             reinterpret_cast<const char*>(passwordRecord.name()), *it});
140         it++;
141     }
142 
143     LOG_MESSAGE("Read yapet file");
144     return result;
145 }
146 
setNewKey(const std::shared_ptr<yapet::AbstractCryptoFactory> & newCryptoFactory,bool forcewrite)147 void File::setNewKey(
148     const std::shared_ptr<yapet::AbstractCryptoFactory>& newCryptoFactory,
149     bool forcewrite) {
150     if (!forcewrite) {
151         notModifiedOrThrow();
152     }
153 
154     bool isSecure = _yapetFile->isSecure();
155     std::string filename{_yapetFile->filename()};
156 
157     // close and destroy old file
158     LOG_MESSAGE("File::setNewKey(): close currently open file");
159     _yapetFile.reset();
160 
161     LOG_MESSAGE("File::setNewKey(): rename file");
162     std::string backupfilename(filename + ".bak");
163     yapet::renameFile(filename, backupfilename);
164 
165     LOG_MESSAGE("File::setNewKey(): open renamed file");
166     std::unique_ptr<yapet::YapetFile> oldFile{
167         _abstractCryptoFactory->file(backupfilename, false, false)};
168     oldFile->open();
169 
170     LOG_MESSAGE("File::setNewKey(): swap crypto factories");
171     auto cryptoFactory{newCryptoFactory};
172     auto otherCrypto{cryptoFactory->crypto()};
173     _abstractCryptoFactory.swap(cryptoFactory);
174 
175     _crypto.swap(otherCrypto);
176 
177     LOG_MESSAGE("File::setNewKey(): create new file");
178     _yapetFile = _abstractCryptoFactory->file(filename, true, isSecure);
179     _yapetFile->open();
180 
181     LOG_MESSAGE("File::setNewKey(): initialize new file");
182     initializeEmptyFile();
183     std::list<yapet::SecureArray> newlyEncryptedRecords{};
184     LOG_MESSAGE("File::setNewKey(): read password records from renamed file");
185     for (auto encryptedSerializedRecord : oldFile->readPasswordRecords()) {
186         auto serializedRecord{otherCrypto->decrypt(encryptedSerializedRecord)};
187         auto newlyEncryptedSerializedRecord{_crypto->encrypt(serializedRecord)};
188         newlyEncryptedRecords.push_back(newlyEncryptedSerializedRecord);
189     }
190     LOG_MESSAGE("File::setNewKey(): write password records to new file");
191     _yapetFile->writePasswordRecords(newlyEncryptedRecords);
192     _fileModificationTime = yapet::getModificationTime(_yapetFile->filename());
193 }
194 
getMasterPWSet()195 int64_t File::getMasterPWSet() {
196     auto header{readHeader()};
197     return header.passwordSetTime();
198 }
199 
200 /**
201  * New since version 0.6.
202  *
203  * Return the file version.
204  */
getFileVersion()205 SecureArray File::getFileVersion() { return _yapetFile->readIdentifier(); }
206 
getHeaderVersion()207 HEADER_VERSION File::getHeaderVersion() {
208     auto encryptedSerializedHeader{_yapetFile->readHeader()};
209     auto serializedHeader{_crypto->decrypt(encryptedSerializedHeader)};
210 
211     Header10 header{serializedHeader};
212     return yapet::intToHeaderVersion(header.version());
213 }
214