1 #include <algorithm>
2 #include <fstream>
3 #include <glibmm.h>
4 #include <openssl/bio.h>
5 #include <openssl/buffer.h>
6 #include <openssl/md5.h>
7 #include <random>
8 #include <sstream>
9 #include <vector>
10 
11 #include "evp_cipher.h"
12 #include "helper.h"
13 #include "keychain.h"
14 
15 namespace {
16 using OpensslKeyData = std::pair<EVPKey, EVPIv>;
17 using SaltData = std::array<uint8_t, 8>;
18 
opensslKey(const std::vector<uint8_t> & password,const SaltData & salt)19 OpensslKeyData opensslKey(const std::vector<uint8_t>& password, const SaltData& salt) {
20     MD5_CTX ctx;
21     EVPKey keyOut;
22     EVPIv ivOut;
23     keyOut.fill(0);
24     ivOut.fill(0);
25 
26     // Initial digest is password + salt;
27     MD5_Init(&ctx);
28     MD5_Update(&ctx, password.data(), password.size());
29     MD5_Update(&ctx, salt.data(), salt.size());
30     MD5_Final(keyOut.data(), &ctx);
31 
32     MD5_Init(&ctx);
33     MD5_Update(&ctx, keyOut.data(), 16);
34     MD5_Update(&ctx, password.data(), password.size());
35     MD5_Update(&ctx, salt.data(), salt.size());
36     MD5_Final(ivOut.data(), &ctx);
37 
38     return OpensslKeyData(std::move(keyOut), std::move(ivOut));
39 }
40 
opensslKeyNoSalt(const std::vector<uint8_t> & password)41 OpensslKeyData opensslKeyNoSalt(const std::vector<uint8_t>& password) {
42     MD5_CTX ctx;
43     EVPKey keyOut;
44     EVPIv ivOut;
45 
46     MD5_Init(&ctx);
47     MD5_Update(&ctx, password.data(), password.size());
48     MD5_Final(keyOut.data(), &ctx);
49 
50     return OpensslKeyData(keyOut, ivOut);
51 }
52 
base64Decode(const std::string & data)53 std::vector<uint8_t> base64Decode(const std::string& data) {
54     std::vector<uint8_t> ret(data.size());
55 
56     auto b64 = BIO_new(BIO_f_base64());
57 #if defined(LIBRESSL_VERSION_NUMBER)
58     auto bmem = BIO_new_mem_buf((char*)data.c_str(), data.size());
59 #else
60     auto bmem = BIO_new_mem_buf(data.c_str(), data.size());
61 #endif
62     bmem = BIO_push(b64, bmem);
63     BIO_set_flags(bmem, BIO_FLAGS_BASE64_NO_NL);
64     int size = BIO_read(bmem, ret.data(), data.size());
65     BIO_free_all(bmem);
66 
67     ret.resize(size);
68     return ret;
69 }
70 
base64Encode(std::vector<uint8_t> & data)71 std::string base64Encode(std::vector<uint8_t>& data) {
72     auto b64 = BIO_push(BIO_new(BIO_f_base64()), BIO_new(BIO_s_mem()));
73     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
74     BIO_write(b64, data.data(), data.size());
75     BIO_flush(b64);
76 
77     BUF_MEM* bptr;
78     BIO_get_mem_ptr(b64, &bptr);
79 
80     std::string ret(bptr->data, bptr->length);
81 
82     BIO_free_all(b64);
83     return ret;
84 }
85 
86 template <typename T>
hasAllKeys(const json & d,T v)87 bool hasAllKeys(const json& d, T v) {
88     return d.find(v) != d.end();
89 }
90 
91 template <typename T, typename... Args>
hasAllKeys(const json & d,T first,Args...args)92 bool hasAllKeys(const json& d, T first, Args... args) {
93     return hasAllKeys(d, first) && hasAllKeys(d, args...);
94 }
95 
generateSalt()96 std::array<uint8_t, 8> generateSalt() {
97     static std::random_device engine;
98 
99     SaltData ret;
100     int cur_index = 0;
101     while (cur_index < ret.size()) {
102         auto cur_random = engine();
103         for (auto bits = (sizeof(cur_random) * 8) - 8; bits != 0; bits -= 8) {
104             uint8_t byte = (cur_random >> bits) & 0xff;
105             ret[cur_index++] = byte;
106         }
107     }
108 
109     return ret;
110 }
111 }
112 
113 using RawKeyData = std::tuple<std::array<uint8_t, 8>, std::vector<uint8_t>, bool>;
parseEncryptedString(const std::string & data)114 RawKeyData parseEncryptedString(const std::string& data) {
115     auto raw_key_data = base64Decode(data);
116     std::array<uint8_t, 8> salt;
117 
118     if (raw_key_data.size() < 8) {
119         throw std::runtime_error("Master key data is too short");
120     }
121 
122     std::string saltCheck(raw_key_data.begin(), raw_key_data.begin() + 8);
123     if (saltCheck == "Salted__") {
124         std::vector<uint8_t> output_data;
125         std::copy_n(raw_key_data.begin() + 8, 8, salt.begin());
126         std::copy(raw_key_data.begin() + 16, raw_key_data.end(), std::back_inserter(output_data));
127         return RawKeyData(salt, output_data, true);
128     }
129     return RawKeyData(salt, raw_key_data, false);
130 }
131 
AgileKeychainMasterKey(const json & input,const std::string masterPassword)132 AgileKeychainMasterKey::AgileKeychainMasterKey(const json& input,
133                                                const std::string masterPassword) {
134     if (!hasAllKeys(input, "data", "iterations", "validation", "level", "identifier"))
135         throw std::runtime_error("Master key data does not have required fields");
136 
137     RawKeyData input_key_data = parseEncryptedString(input["data"]);
138     std::array<uint8_t, 32> master_key;
139 
140     // Generate a 32-byte key from the master password and its salt
141     PKCS5_PBKDF2_HMAC_SHA1(masterPassword.c_str(),
142                            masterPassword.size(),
143                            std::get<0>(input_key_data).data(),
144                            std::get<0>(input_key_data).size(),
145                            input["iterations"],
146                            master_key.size(),
147                            master_key.data());
148 
149     // This block decrypts the master key using the master password and stores it
150     // in ret. If the
151     // master key cannot be decrypted, it raises an error.
152     {
153         try {
154             EVPKey master_aes_key;
155             EVPIv master_aes_iv;
156             std::copy_n(master_key.begin(), 16, master_aes_key.begin());
157             std::copy(master_key.begin() + 16, master_key.end(), master_aes_iv.begin());
158 
159             EVPCipher cipher(EVP_aes_128_cbc(), master_aes_key, master_aes_iv, false);
160             cipher.update(std::get<1>(input_key_data));
161             key_data = std::vector<uint8_t>(cipher.cbegin(), cipher.cend());
162         } catch (EVPCipherException& e) {
163             throw std::runtime_error("Couldn't decrypt master key!");
164         }
165     }
166 
167     OpensslKeyData validation_keys;
168     auto validation_data = parseEncryptedString(input["validation"]);
169     if (std::get<2>(validation_data)) {
170         validation_keys = opensslKey(key_data, std::get<0>(validation_data));
171     } else {
172         validation_keys = opensslKeyNoSalt(key_data);
173     }
174 
175     // This block decrypts the validation key and validates the master key with it
176     {
177         try {
178             EVPCipher cipher(EVP_aes_128_cbc(),
179                              std::get<0>(validation_keys),
180                              std::get<1>(validation_keys),
181                              false);
182             cipher.update(std::get<1>(validation_data));
183             cipher.finalize();
184             if (cipher.accumulator != key_data) {
185                 throw std::runtime_error("Couldn't verify master key!");
186             }
187         } catch (EVPCipherException& e) {
188             throw std::runtime_error("Couldn't decrypt validation_key!");
189         }
190     }
191 
192     level = input["level"];
193     id = input["identifier"];
194 }
195 
decryptItem(const json & input)196 json AgileKeychainMasterKey::decryptItem(const json& input) {
197     return decryptJSON(input["encrypted"]);
198 }
199 
encryptJSON(const json & input)200 std::string AgileKeychainMasterKey::encryptJSON(const json& input) {
201     const auto payload_str = input.dump();
202     const auto new_salt = generateSalt();
203     const auto cipher_keys = opensslKey(key_data, new_salt);
204 
205     std::vector<uint8_t> encrypted_payload;
206     encrypted_payload.reserve(1024);
207     const std::string salted_str = "Salted__";
208     std::copy(salted_str.begin(), salted_str.end(), std::back_inserter(encrypted_payload));
209     std::copy(new_salt.begin(), new_salt.end(), std::back_inserter(encrypted_payload));
210     try {
211         EVPCipher cipher(
212             EVP_aes_128_cbc(), std::get<0>(cipher_keys), std::get<1>(cipher_keys), true);
213 
214         cipher.update(payload_str);
215         std::copy(cipher.begin(), cipher.end(), std::back_inserter(encrypted_payload));
216 
217     } catch (EVPCipherException& e) {
218         throw std::runtime_error("Couldn't encrypt item");
219     }
220 
221     return base64Encode(encrypted_payload);
222 }
223 
decryptJSON(const std::string & input)224 json AgileKeychainMasterKey::decryptJSON(const std::string& input) {
225     auto raw_payload = parseEncryptedString(input);
226     OpensslKeyData cipher_keys;
227     if (std::get<2>(raw_payload)) {
228         cipher_keys = opensslKey(key_data, std::get<0>(raw_payload));
229     } else {
230         cipher_keys = opensslKeyNoSalt(key_data);
231     }
232 
233     try {
234         EVPCipher cipher(
235             EVP_aes_128_cbc(), std::get<0>(cipher_keys), std::get<1>(cipher_keys), false);
236 
237         cipher.update(std::get<1>(raw_payload));
238 
239         const std::string json_str(cipher.cbegin(), cipher.cend());
240         return json::parse(json_str);
241     } catch (EVPCipherException& e) {
242         throw std::runtime_error("Couldn't decrypt item");
243     }
244 }
245 
Keychain(std::string path,std::string masterPassword)246 Keychain::Keychain(std::string path, std::string masterPassword) {
247     json keys_json;
248     // Load the keys file into a json object
249     {
250         std::stringstream keys_path;
251         keys_path << path << "/data/default/encryptionKeys.js";
252         auto encryption_keys_fp = std::ifstream(keys_path.str());
253         if (!encryption_keys_fp)
254             throw std::runtime_error("Error loading encryption keys file");
255         encryption_keys_fp >> keys_json;
256     }
257 
258     auto list = keys_json["list"];
259     if (!list.is_array()) {
260         throw std::runtime_error("Could not find list of keys in keychain");
261     }
262 
263     for (const auto key : list) {
264         std::unique_ptr<AgileKeychainMasterKey> cur_master_key(
265             new AgileKeychainMasterKey(key, masterPassword));
266         if (cur_master_key->level == "SL3")
267             level3_key = std::move(cur_master_key);
268         else if (cur_master_key->level == "SL5")
269             level5_key = std::move(cur_master_key);
270         else {
271             throw std::runtime_error("Unknown security level for master key");
272         }
273     }
274 
275     vault_path = path;
276 }
277 
loadItem(std::string uuid)278 void Keychain::loadItem(std::string uuid) {
279     KeychainItem item;
280     json item_json;
281 
282     {
283         std::stringstream item_path;
284         item_path << vault_path << "/data/default/" << uuid << ".1password";
285         auto item_fp = std::ifstream(item_path.str());
286         if (!item_fp) {
287             throw std::runtime_error("Cannot load item file");
288         }
289         item_fp >> item_json;
290     }
291 
292     auto typeName = item_json["typeName"];
293     std::string securityLevel;
294     if (item_json.find("securityLevel") == item_json.end()) {
295         auto openContents = item_json.find("openContents");
296         if (openContents == item_json.end())
297             throw std::runtime_error("Could not find security level for item");
298         securityLevel = (*openContents)["securityLevel"];
299     } else {
300         if (item_json.find("securityLevel") == item_json.end())
301             throw std::runtime_error("Could not find security level for item");
302         securityLevel = item_json["securityLevel"];
303     }
304 
305     json decrypted_item;
306     if (securityLevel == "SL5")
307         decrypted_item = level5_key->decryptItem(item_json);
308     else if (securityLevel == "SL3")
309         decrypted_item = level3_key->decryptItem(item_json);
310     else
311         throw std::runtime_error("Invalid security level for item");
312 
313     item.title = item_json["title"];
314     item.uuid = uuid;
315 
316     if (decrypted_item.find("notesPlain") != decrypted_item.end())
317         item.notes = decrypted_item["notesPlain"];
318 
319     if (decrypted_item.find("URLs") != decrypted_item.end()) {
320         for (auto url_obj : decrypted_item["URLs"]) {
321             item.URLs.emplace_back(url_obj["url"].get<std::string>());
322         }
323     }
324 
325     if (decrypted_item.find("password") != decrypted_item.end()) {
326         std::string value_str = decrypted_item["password"];
327         item.addField("", {"password", value_str, "P", true});
328     }
329 
330     auto fields = decrypted_item.find("fields");
331     if (fields != decrypted_item.end()) {
332         for (const auto& field : *fields) {
333             if (!hasAllKeys(field, "designation", "value", "type"))
334                 continue;
335             auto typeStr = field["type"];
336             bool isPassword = typeStr == "P";
337             item.addField("", {field["designation"], field["value"], typeStr, isPassword});
338         }
339     }
340 
341     auto sections = decrypted_item.find("sections");
342     if (sections != decrypted_item.end()) {
343         for (const auto& section : *sections) {
344             auto section_fields = section.find("fields");
345             if (section_fields == section.end()) {
346                 continue;
347             }
348 
349             std::string section_title;
350             if (section.find("title") != section.end())
351                 section_title = section["title"];
352 
353             for (const auto& field : *section_fields) {
354                 if (!hasAllKeys(field, "k", "t", "v"))
355                     continue;
356                 std::string typeStr = field["k"];
357                 std::string nameStr = field["t"];
358                 auto value = field["v"];
359                 std::string valueStr;
360                 bool isPassword = typeStr == "concealed";
361 
362                 if (typeStr == "date") {
363                     char dateBuf[20] = {'\0'};
364                     time_t time_val = static_cast<time_t>(value);
365                     std::strftime(dateBuf, sizeof(dateBuf), "%x", std::localtime(&time_val));
366                     valueStr = dateBuf;
367                 } else if (typeStr == "address") {
368                     std::stringstream ss;
369                     if (!value["street"].is_string())
370                         continue;
371 
372                     ss << value["street"].get<std::string>() << " "
373                        << value["city"].get<std::string>() << ", "
374                        << value["state"].get<std::string>() << " "
375                        << value["zip"].get<std::string>();
376                     valueStr = ss.str();
377                 } else if (typeStr == "monthYear") {
378                     auto stringVal = std::to_string(value.get<int>());
379                     std::stringstream ss;
380                     ss << stringVal.substr(0, 4) << "/" << stringVal.substr(4, 2);
381                     valueStr = ss.str();
382                 } else {
383                     valueStr = value;
384                 }
385 
386                 item.addField(section_title, {nameStr, valueStr, typeStr, isPassword});
387             }
388         }
389     }
390 
391     items.insert({uuid, std::move(item)});
392 }
393 
reloadItems()394 void Keychain::reloadItems() {
395     items.clear();
396     json contents_json;
397     {
398         std::stringstream contents_path;
399         contents_path << vault_path << "/data/default/contents.js";
400         auto contents_fp = std::ifstream(contents_path.str());
401         if (!contents_fp)
402             throw std::runtime_error("Cannot open keychain contents");
403         contents_json << contents_fp;
404     }
405 
406     for (const auto& contents_item : contents_json) {
407         if(contents_item[1] != "system.Tombstone") {
408             try {
409                 loadItem(contents_item[0]);
410             } catch (std::exception& e) {
411                 std::stringstream ss;
412                 ss << "Error loading item " << contents_item[2] << ": " << e.what();
413                 errorDialog(ss.str());
414             }
415         }
416     }
417 
418     loaded = true;
419 }
420 
unloadItems()421 void Keychain::unloadItems() {
422     items.clear();
423     loaded = false;
424 }
425