@@ -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