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