Skip to content

Commit 3279bdb

Browse files
committed
Add increment nonce check
1 parent 4598565 commit 3279bdb

File tree

1 file changed

+64
-34
lines changed

1 file changed

+64
-34
lines changed

src/main/java/org/keepassxc/Connection.java

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,11 @@ public void run() {
9393

9494
class MessageConsumer implements Callable<JSONObject> {
9595
private final String action;
96+
private final byte[] nonce;
9697

97-
public MessageConsumer(String action) {
98+
public MessageConsumer(String action, byte[] nonce) {
9899
this.action = action;
100+
this.nonce = nonce;
99101
}
100102

101103
@Override
@@ -106,6 +108,19 @@ public JSONObject call() throws Exception {
106108
Thread.sleep(200);
107109
continue;
108110
}
111+
if (response.has("error")) break;
112+
var qit = queue.iterator();
113+
while (qit.hasNext()) {
114+
var message = qit.next();
115+
if (message.has("action")
116+
&& message.getString("action").equals(action)
117+
&& message.has("nonce")
118+
&& message.getString("nonce").equals(b64encode(incrementNonce(nonce)))) {
119+
queue.remove(message);
120+
log.trace("Retrieved from queue: {}", message);
121+
return message;
122+
}
123+
}
109124
if (isSignal(response)) {
110125
queue.poll();
111126
continue;
@@ -115,8 +130,6 @@ public JSONObject call() throws Exception {
115130
log.trace("KeePassXC send an empty response: {}", response);
116131
continue;
117132
}
118-
if (response.has("error")) break;
119-
if (response.has("action") && response.getString("action").equals(action)) break;
120133
log.trace("Response added to queue: {}", response);
121134
}
122135
log.trace("Retrieved from queue: {}", queue.peek());
@@ -195,10 +208,11 @@ private boolean isSignal(JSONObject response) {
195208
* The proxy accepts messages in the JSON data format.
196209
*
197210
* @param msg The message to be sent. The key "action" describes the request to the proxy.
211+
* @return The nonce that was used for this message.
198212
* @throws IllegalStateException Connection was not initialized before.
199213
* @throws IOException Sending failed due to technical reasons.
200214
*/
201-
private synchronized void sendEncryptedMessage(Map<String, Object> msg) throws IOException {
215+
private synchronized byte[] sendEncryptedMessage(Map<String, Object> msg) throws IOException {
202216
var unlockRequested = false;
203217

204218
if (!isConnected()) {
@@ -232,6 +246,7 @@ private synchronized void sendEncryptedMessage(Map<String, Object> msg) throws I
232246
message.put("triggerUnlock", "true");
233247
}
234248
sendCleartextMessage(jsonTxt(message));
249+
return nonce;
235250

236251
}
237252

@@ -240,14 +255,15 @@ private synchronized void sendEncryptedMessage(Map<String, Object> msg) throws I
240255
* The proxy sends messages in the JSON data format.
241256
*
242257
* @param action The original request that was sent to the proxy.
258+
* @param nonce The original nonce that was part of the request.
243259
* @return The received message, decrypted.
244260
* @throws KeepassProxyAccessException It was impossible to process the requested action.
245261
*/
246-
private synchronized JSONObject getEncryptedResponseAndDecrypt(String action) throws KeepassProxyAccessException {
262+
private synchronized JSONObject getEncryptedResponseAndDecrypt(String action, byte[] nonce) throws KeepassProxyAccessException {
247263
var response = new JSONObject();
248264

249265
try {
250-
response = executorService.submit(new MessageConsumer(action)).get();
266+
response = executorService.submit(new MessageConsumer(action, nonce)).get();
251267
} catch (InterruptedException | ExecutionException e) {
252268
log.error(e.toString(), e.getCause());
253269
}
@@ -299,7 +315,7 @@ protected void changePublicKeys() throws IOException, KeepassProxyAccessExceptio
299315
var response = new JSONObject();
300316

301317
try {
302-
response = executorService.submit(new MessageConsumer("change-public-keys")).get();
318+
response = executorService.submit(new MessageConsumer("change-public-keys", nonce)).get();
303319
} catch (InterruptedException | ExecutionException e) {
304320
log.error(e.toString(), e.getCause());
305321
}
@@ -332,7 +348,7 @@ public void associate() throws IOException, KeepassProxyAccessException {
332348
var keyPair = credentials.orElseThrow(() -> new IllegalStateException(KEYEXCHANGE_MISSING)).getOwnKeypair();
333349

334350
// Send associate request
335-
sendEncryptedMessage(Map.of(
351+
var nonce = sendEncryptedMessage(Map.of(
336352
"action", "associate",
337353
"key", b64encode(keyPair.getPublicKey()),
338354
"idKey", b64encode(idKeyPair.getPublicKey())
@@ -347,7 +363,7 @@ public void associate() throws IOException, KeepassProxyAccessException {
347363
Runnable lookupResponse = () -> {
348364
JSONObject response = null;
349365
try {
350-
response = getEncryptedResponseAndDecrypt("associate");
366+
response = getEncryptedResponseAndDecrypt("associate", nonce);
351367
} catch (KeepassProxyAccessException e) {
352368
log.error(e.toString(), e.getCause());
353369
}
@@ -369,8 +385,8 @@ public void associate() throws IOException, KeepassProxyAccessException {
369385
*/
370386
public String getDatabasehash() throws IOException, KeepassProxyAccessException {
371387
// Send get-databasehash request
372-
sendEncryptedMessage(Map.of("action", "get-databasehash"));
373-
var response = getEncryptedResponseAndDecrypt("get-databasehash");
388+
var nonce = sendEncryptedMessage(Map.of("action", "get-databasehash"));
389+
var response = getEncryptedResponseAndDecrypt("get-databasehash", nonce);
374390

375391
return response.getString("hash");
376392
}
@@ -389,8 +405,8 @@ public String getDatabasehash(boolean triggerUnlock) throws IOException, Keepass
389405
var map = new HashMap<String, Object>(); // Map.of can't be used here, because we need a mutable object
390406
map.put("action", "get-databasehash");
391407
map.put("triggerUnlock", Boolean.toString(triggerUnlock));
392-
sendEncryptedMessage(map);
393-
var response = getEncryptedResponseAndDecrypt("get-databasehash");
408+
var nonce = sendEncryptedMessage(map);
409+
var response = getEncryptedResponseAndDecrypt("get-databasehash", nonce);
394410

395411
return response.getString("hash");
396412
}
@@ -407,12 +423,12 @@ public String getDatabasehash(boolean triggerUnlock) throws IOException, Keepass
407423
*/
408424
public void testAssociate(String id, String key) throws IOException, KeepassProxyAccessException {
409425
// Send test-associate request
410-
sendEncryptedMessage(Map.of(
426+
var nonce = sendEncryptedMessage(Map.of(
411427
"action", "test-associate",
412428
"id", id,
413429
"key", key
414430
));
415-
getEncryptedResponseAndDecrypt("test-associate");
431+
getEncryptedResponseAndDecrypt("test-associate", nonce);
416432

417433
}
418434

@@ -439,14 +455,14 @@ public JSONObject getLogins(String url, String submitUrl, boolean httpAuth, List
439455
}
440456

441457
// Send get-logins
442-
sendEncryptedMessage(Map.of(
458+
var nonce = sendEncryptedMessage(Map.of(
443459
"action", "get-logins",
444460
"url", ensureNotNull(url),
445461
"submitUrl", ensureNotNull(submitUrl),
446462
"httpAuth", httpAuth,
447463
"keys", array
448464
));
449-
return getEncryptedResponseAndDecrypt("get-logins");
465+
return getEncryptedResponseAndDecrypt("get-logins", nonce);
450466

451467
}
452468

@@ -471,7 +487,7 @@ public JSONObject getLogins(String url, String submitUrl, boolean httpAuth, List
471487
*/
472488
public JSONObject setLogin(String url, String submitUrl, String id, String login, String password, String group, String groupUuid, String uuid) throws IOException, KeepassProxyAccessException {
473489
// Send set-login
474-
sendEncryptedMessage(Map.of(
490+
var nonce = sendEncryptedMessage(Map.of(
475491
"action", "set-login",
476492
"url", ensureNotNull(url),
477493
"submitUrl", ensureNotNull(submitUrl),
@@ -482,7 +498,7 @@ public JSONObject setLogin(String url, String submitUrl, String id, String login
482498
"groupUuid", ensureNotNull(groupUuid),
483499
"uuid", ensureNotNull(uuid)
484500
));
485-
return getEncryptedResponseAndDecrypt("set-login");
501+
return getEncryptedResponseAndDecrypt("set-login", nonce);
486502

487503
}
488504

@@ -495,8 +511,8 @@ public JSONObject setLogin(String url, String submitUrl, String id, String login
495511
*/
496512
public JSONObject getDatabaseGroups() throws IOException, KeepassProxyAccessException {
497513
// Send get-database-groups
498-
sendEncryptedMessage(Map.of("action", "get-database-groups"));
499-
return getEncryptedResponseAndDecrypt("get-database-groups");
514+
var nonce = sendEncryptedMessage(Map.of("action", "get-database-groups"));
515+
return getEncryptedResponseAndDecrypt("get-database-groups", nonce);
500516

501517
}
502518

@@ -509,11 +525,11 @@ public JSONObject getDatabaseGroups() throws IOException, KeepassProxyAccessExce
509525
*/
510526
public JSONObject generatePassword() throws IOException, KeepassProxyAccessException {
511527
// Send generate-password request
512-
sendEncryptedMessage(Map.of(
528+
var nonce = sendEncryptedMessage(Map.of(
513529
"action", "generate-password",
514530
"clientID", clientID
515531
));
516-
return getEncryptedResponseAndDecrypt("generate-password");
532+
return getEncryptedResponseAndDecrypt("generate-password", nonce);
517533

518534
}
519535

@@ -526,8 +542,8 @@ public JSONObject generatePassword() throws IOException, KeepassProxyAccessExcep
526542
*/
527543
public JSONObject lockDatabase() throws IOException, KeepassProxyAccessException {
528544
// Send lock-database request
529-
sendEncryptedMessage(Map.of("action", "lock-database"));
530-
return getEncryptedResponseAndDecrypt("lock-database");
545+
var nonce = sendEncryptedMessage(Map.of("action", "lock-database"));
546+
return getEncryptedResponseAndDecrypt("lock-database", nonce);
531547

532548
}
533549

@@ -543,11 +559,11 @@ public JSONObject lockDatabase() throws IOException, KeepassProxyAccessException
543559
*/
544560
public JSONObject createNewGroup(String path) throws IOException, KeepassProxyAccessException {
545561
// Send create-new-group request
546-
sendEncryptedMessage(Map.of(
562+
var nonce = sendEncryptedMessage(Map.of(
547563
"action", "create-new-group",
548564
"groupName", ensureNotNull(path)
549565
));
550-
return getEncryptedResponseAndDecrypt("create-new-group");
566+
return getEncryptedResponseAndDecrypt("create-new-group", nonce);
551567

552568
}
553569

@@ -562,11 +578,11 @@ public JSONObject createNewGroup(String path) throws IOException, KeepassProxyAc
562578
*/
563579
public JSONObject getTotp(String uuid) throws IOException, KeepassProxyAccessException {
564580
// Send get-totp request
565-
sendEncryptedMessage(Map.of(
581+
var nonce = sendEncryptedMessage(Map.of(
566582
"action", "get-totp",
567583
"uuid", ensureNotNull(uuid)
568584
));
569-
return getEncryptedResponseAndDecrypt("get-totp");
585+
return getEncryptedResponseAndDecrypt("get-totp", nonce);
570586

571587
}
572588

@@ -580,11 +596,11 @@ public JSONObject getTotp(String uuid) throws IOException, KeepassProxyAccessExc
580596
*/
581597
public JSONObject deleteEntry(String uuid) throws IOException, KeepassProxyAccessException {
582598
// Send delete-entry request
583-
sendEncryptedMessage(Map.of(
599+
var nonce = sendEncryptedMessage(Map.of(
584600
"action", "delete-entry",
585601
"uuid", ensureNotNull(uuid)
586602
));
587-
return getEncryptedResponseAndDecrypt("delete-entry");
603+
return getEncryptedResponseAndDecrypt("delete-entry", nonce);
588604
}
589605

590606
/**
@@ -597,11 +613,11 @@ public JSONObject deleteEntry(String uuid) throws IOException, KeepassProxyAcces
597613
*/
598614
public JSONObject requestAutotype(String url) throws IOException, KeepassProxyAccessException {
599615
// Send request-autotype request
600-
sendEncryptedMessage(Map.of(
616+
var nonce = sendEncryptedMessage(Map.of(
601617
"action", "request-autotype",
602618
"groupName", ensureNotNull(url)
603619
));
604-
return getEncryptedResponseAndDecrypt("request-autotype");
620+
return getEncryptedResponseAndDecrypt("request-autotype", nonce);
605621

606622
}
607623

@@ -624,10 +640,24 @@ private byte[] ramdomGenerateNonce() {
624640
return TweetNaclFast.randombytes(nonceLength);
625641
}
626642

643+
/**
644+
* Increment a nonce by 1 like in libsodium/utils.c
645+
*
646+
* @param nonce The nonce to be incremented.
647+
* @return nonce "+1".
648+
*/
649+
private byte[] incrementNonce(byte[] nonce) {
650+
var c = 1;
651+
byte[] incrementedNonce = nonce.clone();
652+
c += incrementedNonce[0];
653+
incrementedNonce[0] = (byte) c;
654+
return incrementedNonce;
655+
}
656+
627657
/**
628658
* Base64 encode array of bytes and wrap as a String.
629659
*
630-
* @param bytes The data to be ecoded.
660+
* @param bytes The data to be encoded.
631661
* @return Base64 encoded String.
632662
*/
633663
private String b64encode(byte[] bytes) {

0 commit comments

Comments
 (0)