1 /*
2 * Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "BrowserAction.h"
19 #include "BrowserService.h"
20 #include "BrowserSettings.h"
21 #include "BrowserShared.h"
22 #include "config-keepassx.h"
23 #include "core/Global.h"
24
25 #include <QJsonDocument>
26 #include <QJsonParseError>
27 #include <sodium.h>
28 #include <sodium/crypto_box.h>
29 #include <sodium/randombytes.h>
30
31 namespace
32 {
33 enum
34 {
35 ERROR_KEEPASS_DATABASE_NOT_OPENED = 1,
36 ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED = 2,
37 ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED = 3,
38 ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE = 4,
39 ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED = 5,
40 ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED = 6,
41 ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE = 7,
42 ERROR_KEEPASS_ASSOCIATION_FAILED = 8,
43 ERROR_KEEPASS_KEY_CHANGE_FAILED = 9,
44 ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED = 10,
45 ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND = 11,
46 ERROR_KEEPASS_INCORRECT_ACTION = 12,
47 ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
48 ERROR_KEEPASS_NO_URL_PROVIDED = 14,
49 ERROR_KEEPASS_NO_LOGINS_FOUND = 15,
50 ERROR_KEEPASS_NO_GROUPS_FOUND = 16,
51 ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP = 17
52 };
53 }
54
processClientMessage(const QJsonObject & json)55 QJsonObject BrowserAction::processClientMessage(const QJsonObject& json)
56 {
57 if (json.isEmpty()) {
58 return getErrorReply("", ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED);
59 }
60
61 bool triggerUnlock = false;
62 const QString trigger = json.value("triggerUnlock").toString();
63 if (!trigger.isEmpty() && trigger.compare(TRUE_STR, Qt::CaseSensitive) == 0) {
64 triggerUnlock = true;
65 }
66
67 const QString action = json.value("action").toString();
68 if (action.isEmpty()) {
69 return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
70 }
71
72 if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !browserService()->isDatabaseOpened()) {
73 if (m_clientPublicKey.isEmpty()) {
74 return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
75 } else if (!browserService()->openDatabase(triggerUnlock)) {
76 return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
77 }
78 }
79
80 return handleAction(json);
81 }
82
83 // Private functions
84 ///////////////////////
85
handleAction(const QJsonObject & json)86 QJsonObject BrowserAction::handleAction(const QJsonObject& json)
87 {
88 QString action = json.value("action").toString();
89
90 if (action.compare("change-public-keys", Qt::CaseSensitive) == 0) {
91 return handleChangePublicKeys(json, action);
92 } else if (action.compare("get-databasehash", Qt::CaseSensitive) == 0) {
93 return handleGetDatabaseHash(json, action);
94 } else if (action.compare("associate", Qt::CaseSensitive) == 0) {
95 return handleAssociate(json, action);
96 } else if (action.compare("test-associate", Qt::CaseSensitive) == 0) {
97 return handleTestAssociate(json, action);
98 } else if (action.compare("get-logins", Qt::CaseSensitive) == 0) {
99 return handleGetLogins(json, action);
100 } else if (action.compare("generate-password", Qt::CaseSensitive) == 0) {
101 return handleGeneratePassword(json, action);
102 } else if (action.compare("set-login", Qt::CaseSensitive) == 0) {
103 return handleSetLogin(json, action);
104 } else if (action.compare("lock-database", Qt::CaseSensitive) == 0) {
105 return handleLockDatabase(json, action);
106 } else if (action.compare("get-database-groups", Qt::CaseSensitive) == 0) {
107 return handleGetDatabaseGroups(json, action);
108 } else if (action.compare("create-new-group", Qt::CaseSensitive) == 0) {
109 return handleCreateNewGroup(json, action);
110 } else if (action.compare("get-totp", Qt::CaseSensitive) == 0) {
111 return handleGetTotp(json, action);
112 }
113
114 // Action was not recognized
115 return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
116 }
117
handleChangePublicKeys(const QJsonObject & json,const QString & action)118 QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const QString& action)
119 {
120 const QString nonce = json.value("nonce").toString();
121 const QString clientPublicKey = json.value("publicKey").toString();
122
123 if (clientPublicKey.isEmpty() || nonce.isEmpty()) {
124 return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
125 }
126
127 m_associated = false;
128 unsigned char pk[crypto_box_PUBLICKEYBYTES];
129 unsigned char sk[crypto_box_SECRETKEYBYTES];
130 crypto_box_keypair(pk, sk);
131
132 const QString publicKey = getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES);
133 const QString secretKey = getBase64FromKey(sk, crypto_box_SECRETKEYBYTES);
134 if (publicKey.isEmpty() || secretKey.isEmpty()) {
135 return getErrorReply(action, ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED);
136 }
137
138 m_clientPublicKey = clientPublicKey;
139 m_publicKey = publicKey;
140 m_secretKey = secretKey;
141
142 QJsonObject response = buildMessage(incrementNonce(nonce));
143 response["action"] = action;
144 response["publicKey"] = publicKey;
145
146 return response;
147 }
148
handleGetDatabaseHash(const QJsonObject & json,const QString & action)149 QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const QString& action)
150 {
151 const QString hash = browserService()->getDatabaseHash();
152 const QString nonce = json.value("nonce").toString();
153 const QString encrypted = json.value("message").toString();
154 const QJsonObject decrypted = decryptMessage(encrypted, nonce);
155
156 if (decrypted.isEmpty()) {
157 return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
158 }
159
160 if (hash.isEmpty()) {
161 return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
162 }
163
164 QString command = decrypted.value("action").toString();
165 if (!command.isEmpty() && command.compare("get-databasehash", Qt::CaseSensitive) == 0) {
166 const QString newNonce = incrementNonce(nonce);
167
168 QJsonObject message = buildMessage(newNonce);
169 message["hash"] = hash;
170
171 // Update a legacy database hash if found
172 const QJsonArray hashes = decrypted.value("connectedKeys").toArray();
173 if (!hashes.isEmpty()) {
174 const QString legacyHash = browserService()->getDatabaseHash(true);
175 if (hashes.contains(legacyHash)) {
176 message["oldHash"] = legacyHash;
177 }
178 }
179
180 return buildResponse(action, message, newNonce);
181 }
182
183 return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
184 }
185
handleAssociate(const QJsonObject & json,const QString & action)186 QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QString& action)
187 {
188 const QString hash = browserService()->getDatabaseHash();
189 const QString nonce = json.value("nonce").toString();
190 const QString encrypted = json.value("message").toString();
191 const QJsonObject decrypted = decryptMessage(encrypted, nonce);
192
193 if (decrypted.isEmpty()) {
194 return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
195 }
196
197 const QString key = decrypted.value("key").toString();
198 if (key.isEmpty()) {
199 return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
200 }
201
202 if (key.compare(m_clientPublicKey, Qt::CaseSensitive) == 0) {
203 // Check for identification key. If it's not found, ensure backwards compatibility and use the current public
204 // key
205 const QString idKey = decrypted.value("idKey").toString();
206 const QString id = browserService()->storeKey((idKey.isEmpty() ? key : idKey));
207 if (id.isEmpty()) {
208 return getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
209 }
210
211 m_associated = true;
212 const QString newNonce = incrementNonce(nonce);
213
214 QJsonObject message = buildMessage(newNonce);
215 message["hash"] = hash;
216 message["id"] = id;
217 return buildResponse(action, message, newNonce);
218 }
219
220 return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
221 }
222
handleTestAssociate(const QJsonObject & json,const QString & action)223 QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QString& action)
224 {
225 const QString hash = browserService()->getDatabaseHash();
226 const QString nonce = json.value("nonce").toString();
227 const QString encrypted = json.value("message").toString();
228 const QJsonObject decrypted = decryptMessage(encrypted, nonce);
229
230 if (decrypted.isEmpty()) {
231 return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
232 }
233
234 const QString responseKey = decrypted.value("key").toString();
235 const QString id = decrypted.value("id").toString();
236 if (responseKey.isEmpty() || id.isEmpty()) {
237 return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
238 }
239
240 const QString key = browserService()->getKey(id);
241 if (key.isEmpty() || key.compare(responseKey, Qt::CaseSensitive) != 0) {
242 return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
243 }
244
245 m_associated = true;
246 const QString newNonce = incrementNonce(nonce);
247
248 QJsonObject message = buildMessage(newNonce);
249 message["hash"] = hash;
250 message["id"] = id;
251
252 return buildResponse(action, message, newNonce);
253 }
254
handleGetLogins(const QJsonObject & json,const QString & action)255 QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QString& action)
256 {
257 const QString hash = browserService()->getDatabaseHash();
258 const QString nonce = json.value("nonce").toString();
259 const QString encrypted = json.value("message").toString();
260
261 if (!m_associated) {
262 return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
263 }
264
265 const QJsonObject decrypted = decryptMessage(encrypted, nonce);
266 if (decrypted.isEmpty()) {
267 return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
268 }
269
270 const QString siteUrl = decrypted.value("url").toString();
271 if (siteUrl.isEmpty()) {
272 return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
273 }
274
275 const QJsonArray keys = decrypted.value("keys").toArray();
276
277 StringPairList keyList;
278 for (const QJsonValue val : keys) {
279 const QJsonObject keyObject = val.toObject();
280 keyList.push_back(qMakePair(keyObject.value("id").toString(), keyObject.value("key").toString()));
281 }
282
283 const QString id = decrypted.value("id").toString();
284 const QString formUrl = decrypted.value("submitUrl").toString();
285 const QString auth = decrypted.value("httpAuth").toString();
286 const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0 ? true : false;
287 const QJsonArray users = browserService()->findMatchingEntries(id, siteUrl, formUrl, "", keyList, httpAuth);
288
289 if (users.isEmpty()) {
290 return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND);
291 }
292
293 const QString newNonce = incrementNonce(nonce);
294
295 QJsonObject message = buildMessage(newNonce);
296 message["count"] = users.count();
297 message["entries"] = users;
298 message["hash"] = hash;
299 message["id"] = id;
300
301 return buildResponse(action, message, newNonce);
302 }
303
handleGeneratePassword(const QJsonObject & json,const QString & action)304 QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const QString& action)
305 {
306 auto nonce = json.value("nonce").toString();
307 auto password = browserSettings()->generatePassword();
308
309 if (nonce.isEmpty() || password.isEmpty()) {
310 return QJsonObject();
311 }
312
313 // For backwards compatibility
314 password["login"] = password["entropy"];
315
316 QJsonArray arr;
317 arr.append(password);
318
319 const QString newNonce = incrementNonce(nonce);
320
321 QJsonObject message = buildMessage(newNonce);
322 message["entries"] = arr;
323
324 return buildResponse(action, message, newNonce);
325 }
326
handleSetLogin(const QJsonObject & json,const QString & action)327 QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString& action)
328 {
329 const QString hash = browserService()->getDatabaseHash();
330 const QString nonce = json.value("nonce").toString();
331 const QString encrypted = json.value("message").toString();
332
333 if (!m_associated) {
334 return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
335 }
336
337 const QJsonObject decrypted = decryptMessage(encrypted, nonce);
338 if (decrypted.isEmpty()) {
339 return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
340 }
341
342 const QString url = decrypted.value("url").toString();
343 if (url.isEmpty()) {
344 return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
345 }
346
347 const QString id = decrypted.value("id").toString();
348 const QString login = decrypted.value("login").toString();
349 const QString password = decrypted.value("password").toString();
350 const QString submitUrl = decrypted.value("submitUrl").toString();
351 const QString uuid = decrypted.value("uuid").toString();
352 const QString group = decrypted.value("group").toString();
353 const QString groupUuid = decrypted.value("groupUuid").toString();
354 const QString realm;
355
356 bool result = true;
357 if (uuid.isEmpty()) {
358 browserService()->addEntry(id, login, password, url, submitUrl, realm, group, groupUuid);
359 } else {
360 result = browserService()->updateEntry(id, uuid, login, password, url, submitUrl);
361 }
362
363 const QString newNonce = incrementNonce(nonce);
364
365 QJsonObject message = buildMessage(newNonce);
366 message["count"] = QJsonValue::Null;
367 message["entries"] = QJsonValue::Null;
368 message["error"] = result ? QStringLiteral("success") : QStringLiteral("error");
369 message["hash"] = hash;
370
371 return buildResponse(action, message, newNonce);
372 }
373
handleLockDatabase(const QJsonObject & json,const QString & action)374 QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QString& action)
375 {
376 const QString hash = browserService()->getDatabaseHash();
377 const QString nonce = json.value("nonce").toString();
378 const QString encrypted = json.value("message").toString();
379 const QJsonObject decrypted = decryptMessage(encrypted, nonce);
380
381 if (decrypted.isEmpty()) {
382 return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
383 }
384
385 if (hash.isEmpty()) {
386 return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
387 }
388
389 QString command = decrypted.value("action").toString();
390 if (!command.isEmpty() && command.compare("lock-database", Qt::CaseSensitive) == 0) {
391 browserService()->lockDatabase();
392
393 const QString newNonce = incrementNonce(nonce);
394 QJsonObject message = buildMessage(newNonce);
395
396 return buildResponse(action, message, newNonce);
397 }
398
399 return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
400 }
401
handleGetDatabaseGroups(const QJsonObject & json,const QString & action)402 QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, const QString& action)
403 {
404 const QString hash = browserService()->getDatabaseHash();
405 const QString nonce = json.value("nonce").toString();
406 const QString encrypted = json.value("message").toString();
407
408 if (!m_associated) {
409 return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
410 }
411
412 const QJsonObject decrypted = decryptMessage(encrypted, nonce);
413 if (decrypted.isEmpty()) {
414 return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
415 }
416
417 QString command = decrypted.value("action").toString();
418 if (command.isEmpty() || command.compare("get-database-groups", Qt::CaseSensitive) != 0) {
419 return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
420 }
421
422 const QJsonObject groups = browserService()->getDatabaseGroups();
423 if (groups.isEmpty()) {
424 return getErrorReply(action, ERROR_KEEPASS_NO_GROUPS_FOUND);
425 }
426
427 const QString newNonce = incrementNonce(nonce);
428
429 QJsonObject message = buildMessage(newNonce);
430 message["groups"] = groups;
431
432 return buildResponse(action, message, newNonce);
433 }
434
handleCreateNewGroup(const QJsonObject & json,const QString & action)435 QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const QString& action)
436 {
437 const QString hash = browserService()->getDatabaseHash();
438 const QString nonce = json.value("nonce").toString();
439 const QString encrypted = json.value("message").toString();
440
441 if (!m_associated) {
442 return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
443 }
444
445 const QJsonObject decrypted = decryptMessage(encrypted, nonce);
446 if (decrypted.isEmpty()) {
447 return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
448 }
449
450 QString command = decrypted.value("action").toString();
451 if (command.isEmpty() || command.compare("create-new-group", Qt::CaseSensitive) != 0) {
452 return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
453 }
454
455 QString group = decrypted.value("groupName").toString();
456 const QJsonObject newGroup = browserService()->createNewGroup(group);
457 if (newGroup.isEmpty() || newGroup["name"].toString().isEmpty() || newGroup["uuid"].toString().isEmpty()) {
458 return getErrorReply(action, ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP);
459 }
460
461 const QString newNonce = incrementNonce(nonce);
462
463 QJsonObject message = buildMessage(newNonce);
464 message["name"] = newGroup["name"];
465 message["uuid"] = newGroup["uuid"];
466
467 return buildResponse(action, message, newNonce);
468 }
469
handleGetTotp(const QJsonObject & json,const QString & action)470 QJsonObject BrowserAction::handleGetTotp(const QJsonObject& json, const QString& action)
471 {
472 const QString nonce = json.value("nonce").toString();
473 const QString encrypted = json.value("message").toString();
474
475 if (!m_associated) {
476 return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
477 }
478
479 const QJsonObject decrypted = decryptMessage(encrypted, nonce);
480 if (decrypted.isEmpty()) {
481 return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
482 }
483
484 QString command = decrypted.value("action").toString();
485 if (command.isEmpty() || command.compare("get-totp", Qt::CaseSensitive) != 0) {
486 return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
487 }
488
489 const QString uuid = decrypted.value("uuid").toString();
490
491 // Get the current TOTP
492 const auto totp = browserService()->getCurrentTotp(uuid);
493 const QString newNonce = incrementNonce(nonce);
494
495 QJsonObject message = buildMessage(newNonce);
496 message["totp"] = totp;
497
498 return buildResponse(action, message, newNonce);
499 }
500
getErrorReply(const QString & action,const int errorCode) const501 QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const
502 {
503 QJsonObject response;
504 response["action"] = action;
505 response["errorCode"] = QString::number(errorCode);
506 response["error"] = getErrorMessage(errorCode);
507 return response;
508 }
509
buildMessage(const QString & nonce) const510 QJsonObject BrowserAction::buildMessage(const QString& nonce) const
511 {
512 QJsonObject message;
513 message["version"] = KEEPASSXC_VERSION;
514 message["success"] = TRUE_STR;
515 message["nonce"] = nonce;
516 return message;
517 }
518
buildResponse(const QString & action,const QJsonObject & message,const QString & nonce)519 QJsonObject BrowserAction::buildResponse(const QString& action, const QJsonObject& message, const QString& nonce)
520 {
521 QJsonObject response;
522 QString encryptedMessage = encryptMessage(message, nonce);
523 if (encryptedMessage.isEmpty()) {
524 return getErrorReply(action, ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE);
525 }
526
527 response["action"] = action;
528 response["message"] = encryptedMessage;
529 response["nonce"] = nonce;
530 return response;
531 }
532
getErrorMessage(const int errorCode) const533 QString BrowserAction::getErrorMessage(const int errorCode) const
534 {
535 switch (errorCode) {
536 case ERROR_KEEPASS_DATABASE_NOT_OPENED:
537 return QObject::tr("Database not opened");
538 case ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED:
539 return QObject::tr("Database hash not available");
540 case ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED:
541 return QObject::tr("Client public key not received");
542 case ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE:
543 return QObject::tr("Cannot decrypt message");
544 case ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED:
545 return QObject::tr("Action cancelled or denied");
546 case ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE:
547 return QObject::tr("Message encryption failed.");
548 case ERROR_KEEPASS_ASSOCIATION_FAILED:
549 return QObject::tr("KeePassXC association failed, try again");
550 case ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED:
551 return QObject::tr("Encryption key is not recognized");
552 case ERROR_KEEPASS_INCORRECT_ACTION:
553 return QObject::tr("Incorrect action");
554 case ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED:
555 return QObject::tr("Empty message received");
556 case ERROR_KEEPASS_NO_URL_PROVIDED:
557 return QObject::tr("No URL provided");
558 case ERROR_KEEPASS_NO_LOGINS_FOUND:
559 return QObject::tr("No logins found");
560 case ERROR_KEEPASS_NO_GROUPS_FOUND:
561 return QObject::tr("No groups found");
562 case ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP:
563 return QObject::tr("Cannot create new group");
564 default:
565 return QObject::tr("Unknown error");
566 }
567 }
568
encryptMessage(const QJsonObject & message,const QString & nonce)569 QString BrowserAction::encryptMessage(const QJsonObject& message, const QString& nonce)
570 {
571 if (message.isEmpty() || nonce.isEmpty()) {
572 return QString();
573 }
574
575 const QString reply(QJsonDocument(message).toJson());
576 if (!reply.isEmpty()) {
577 return encrypt(reply, nonce);
578 }
579
580 return QString();
581 }
582
decryptMessage(const QString & message,const QString & nonce)583 QJsonObject BrowserAction::decryptMessage(const QString& message, const QString& nonce)
584 {
585 if (message.isEmpty() || nonce.isEmpty()) {
586 return QJsonObject();
587 }
588
589 QByteArray ba = decrypt(message, nonce);
590 if (ba.isEmpty()) {
591 return QJsonObject();
592 }
593
594 return getJsonObject(ba);
595 }
596
encrypt(const QString & plaintext,const QString & nonce)597 QString BrowserAction::encrypt(const QString& plaintext, const QString& nonce)
598 {
599 const QByteArray ma = plaintext.toUtf8();
600 const QByteArray na = base64Decode(nonce);
601 const QByteArray ca = base64Decode(m_clientPublicKey);
602 const QByteArray sa = base64Decode(m_secretKey);
603
604 std::vector<unsigned char> m(ma.cbegin(), ma.cend());
605 std::vector<unsigned char> n(na.cbegin(), na.cend());
606 std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
607 std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
608
609 std::vector<unsigned char> e;
610 e.resize(BrowserShared::NATIVEMSG_MAX_LENGTH);
611
612 if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
613 return QString();
614 }
615
616 if (crypto_box_easy(e.data(), m.data(), m.size(), n.data(), ck.data(), sk.data()) == 0) {
617 QByteArray res = getQByteArray(e.data(), (crypto_box_MACBYTES + ma.length()));
618 return res.toBase64();
619 }
620
621 return QString();
622 }
623
decrypt(const QString & encrypted,const QString & nonce)624 QByteArray BrowserAction::decrypt(const QString& encrypted, const QString& nonce)
625 {
626 const QByteArray ma = base64Decode(encrypted);
627 const QByteArray na = base64Decode(nonce);
628 const QByteArray ca = base64Decode(m_clientPublicKey);
629 const QByteArray sa = base64Decode(m_secretKey);
630
631 std::vector<unsigned char> m(ma.cbegin(), ma.cend());
632 std::vector<unsigned char> n(na.cbegin(), na.cend());
633 std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
634 std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
635
636 std::vector<unsigned char> d;
637 d.resize(BrowserShared::NATIVEMSG_MAX_LENGTH);
638
639 if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
640 return QByteArray();
641 }
642
643 if (crypto_box_open_easy(d.data(), m.data(), ma.length(), n.data(), ck.data(), sk.data()) == 0) {
644 return getQByteArray(d.data(), std::char_traits<char>::length(reinterpret_cast<const char*>(d.data())));
645 }
646
647 return QByteArray();
648 }
649
getBase64FromKey(const uchar * array,const uint len)650 QString BrowserAction::getBase64FromKey(const uchar* array, const uint len)
651 {
652 return getQByteArray(array, len).toBase64();
653 }
654
getQByteArray(const uchar * array,const uint len) const655 QByteArray BrowserAction::getQByteArray(const uchar* array, const uint len) const
656 {
657 QByteArray qba;
658 qba.reserve(len);
659 for (uint i = 0; i < len; ++i) {
660 qba.append(static_cast<char>(array[i]));
661 }
662 return qba;
663 }
664
getJsonObject(const uchar * pArray,const uint len) const665 QJsonObject BrowserAction::getJsonObject(const uchar* pArray, const uint len) const
666 {
667 QByteArray arr = getQByteArray(pArray, len);
668 QJsonParseError err;
669 QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
670 return doc.object();
671 }
672
getJsonObject(const QByteArray & ba) const673 QJsonObject BrowserAction::getJsonObject(const QByteArray& ba) const
674 {
675 QJsonParseError err;
676 QJsonDocument doc(QJsonDocument::fromJson(ba, &err));
677 return doc.object();
678 }
679
base64Decode(const QString & str)680 QByteArray BrowserAction::base64Decode(const QString& str)
681 {
682 return QByteArray::fromBase64(str.toUtf8());
683 }
684
incrementNonce(const QString & nonce)685 QString BrowserAction::incrementNonce(const QString& nonce)
686 {
687 const QByteArray nonceArray = base64Decode(nonce);
688 std::vector<unsigned char> n(nonceArray.cbegin(), nonceArray.cend());
689
690 sodium_increment(n.data(), n.size());
691 return getQByteArray(n.data(), n.size()).toBase64();
692 }
693