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