Skip to content

Commit 1c4fac3

Browse files
committed
Implement passkeys-get request
1 parent 29446e1 commit 1c4fac3

File tree

4 files changed

+87
-1
lines changed

4 files changed

+87
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ Communication with KeePassXC happens via the KeePassXC protocol. Currently, the
5050
* `get-totp`: Request for receiving the current TOTP.
5151
* `delete-entry`: Request for deleting an entry in the database, identified by its uuid (KeePassXC 2.7.0 and newer).
5252
* `request-autotype`: Request autotype from the KeePassXC database (KeePassXC 2.7.0 and newer).
53+
* `passkeys-get`: Request for Passkeys authentication (KeePassXC 2.8.0 and newer).
54+
* `passkeys-register`: Request for Passkeys credential registration (KeePassXC 2.8.0 and newer).
5355
* `database-locked`: A signal from KeePassXC, the current active database is locked.
5456
* `database-unlocked`: A signal from KeePassXC, the current active database is unlocked.
5557

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,44 @@ public JSONObject passkeysRegister(JSONObject publicKey, String origin, List<Map
708708

709709
}
710710

711+
/**
712+
* Request passkeys-get from the KeePassXC database (KeePassXC 2.8.0 and newer).
713+
* @param publicKey An object containing all required information for the public key.
714+
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API">Web Authentication API</a> for publicKey contents.
715+
* @param origin The origin the request originates from in the form {@code https://...}
716+
* @param list A list of pairs of associateID and IDKeyPublicKey stored on association.
717+
* @return An object that contains the result of the operation. In case authenticating with the Passkey was successful, the response
718+
* looks like: <pre>{@code "response": {
719+
* "authenticatorAttachment": "platform",
720+
* "id": "tX6nBMj5Ksxg6QnTL1ilSwipm_up7rIiYsIQmYTKIAg",
721+
* "response": {
722+
* "authenticatorData": "5Yaf4EYzO6ALp_K7s-p-BQLPSCYVYcKLZptoXwxqQzsFAAAAAA",
723+
* "clientDataJSON": "eyJj...",
724+
* "signature": "MEYC...",
725+
* "userHandle": "DEMO__9fX19ERU1P"
726+
* },
727+
* "type": "public-key"
728+
* }}</pre>
729+
* In case the authentication failed, the response looks like: <pre>{@code "response": {
730+
* "errorCode": 15
731+
* }}</pre>
732+
* @throws IOException The passkeys-get request failed due to technical reasons.
733+
* @throws KeepassProxyAccessException The request could not be processed.
734+
*/
735+
public JSONObject passkeysGet(JSONObject publicKey, String origin, List<Map<String, String>> list) throws IOException, KeepassProxyAccessException {
736+
var jsonArray = checkKeysList(list);
737+
738+
// Send passkeys-get request
739+
var nonce = sendEncryptedMessage(Map.of(
740+
"action", Message.PASSKEYS_GET.action,
741+
"publicKey", publicKey,
742+
"origin", ensureNotNull(origin),
743+
"keys", jsonArray
744+
));
745+
return getEncryptedResponseAndDecrypt(Message.PASSKEYS_GET.action, nonce);
746+
747+
}
748+
711749
/**
712750
* Get a String representation of the JSON object.
713751
*

src/main/java/org/purejava/KeepassProxyAccess.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,48 @@ public JSONObject passkeysRegister(JSONObject publicKey, String origin, List<Map
464464
return new JSONObject();
465465
}
466466

467+
/**
468+
* Request passkeys-get from the KeePassXC database (KeePassXC 2.8.0 and newer).
469+
* @param publicKey An object containing all required information for the public key.
470+
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API">Web Authentication API</a> for publicKey contents.
471+
* @param origin The origin the request originates from in the form {@code https://...}
472+
* @param list A list of pairs of associateID and IDKeyPublicKey stored on association.
473+
* @return An object that contains the result of the operation. In case authenticating with the Passkey was successful, the response
474+
* looks like: <pre>{@code "response": {
475+
* "authenticatorAttachment": "platform",
476+
* "id": "tX6nBMj5Ksxg6QnTL1ilSwipm_up7rIiYsIQmYTKIAg",
477+
* "response": {
478+
* "authenticatorData": "5Yaf4EYzO6ALp_K7s-p-BQLPSCYVYcKLZptoXwxqQzsFAAAAAA",
479+
* "clientDataJSON": "eyJj...",
480+
* "signature": "MEYC...",
481+
* "userHandle": "DEMO__9fX19ERU1P"
482+
* },
483+
* "type": "public-key"
484+
* }}</pre>
485+
* In case the authentication failed, the response looks like: <pre>{@code "response": {
486+
* "errorCode": 15
487+
* }}</pre>
488+
*/
489+
public JSONObject passkeysGet(JSONObject publicKey, String origin, List<Map<String, String>> list) {
490+
try {
491+
var response = connection.passkeysGet(publicKey, origin, list);
492+
if (response.has("response") && response.has("success") && response.getString("success").equals("true")) {
493+
try {
494+
var errorCode = response.getJSONObject("response").getInt("errorCode");
495+
throw new KeepassProxyAccessException("ErrorCode: " + errorCode);
496+
497+
} catch (JSONException e) {
498+
return response.getJSONObject("response"); // PublicKeyCredential
499+
}
500+
} else {
501+
return new JSONObject();
502+
}
503+
} catch (IOException | KeepassProxyAccessException e) {
504+
LOG.info(e.toString(), e.getCause());
505+
}
506+
return new JSONObject();
507+
}
508+
467509
/**
468510
* Extract the groupUuid for the newly created group.
469511
* Note: in case a group with the following path was created: level1/level2, only level2 gets returned as name.

src/test/java/org/purejava/UnlockedDatabaseTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ public void shouldHaveNoErrors() throws InterruptedException {
5454
LOG.info("Please register the offered Passkey");
5555
String publicKey = "{\"attestation\":\"direct\",\"authenticatorSelection\":{\"requireResidentKey\":true,\"residentKey\":\"required\",\"userVerification\":\"preferred\"},\"challenge\":\"AICQS3rj6P-dIDb5if3OCte-Y7CEs_BEnpTgoasQRXg\",\"excludeCredentials\":[],\"extensions\":{\"credProps\":true},\"pubKeyCredParams\":[{\"alg\":-7,\"type\":\"public-key\"},{\"alg\":-257,\"type\":\"public-key\"}],\"rp\":{\"id\":\"passkey.org\",\"name\":\"Yubico Demo\"},\"timeout\":90000,\"user\":{\"displayName\":\"purejava\",\"id\":\"DEMO__9fX19ERU1P\",\"name\":\"purejava\"}}";
5656
JSONObject p = new JSONObject(publicKey);
57-
assertTrue(kpa.passkeysRegister(p, "https://passkey.org", l).getJSONObject("response").getString("clientDataJSON").equals("eyJjaGFsbGVuZ2UiOiJBSUNRUzNyajZQLWRJRGI1aWYzT0N0ZS1ZN0NFc19CRW5wVGdvYXNRUlhnIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvcmlnaW4iOiJodHRwczovL3Bhc3NrZXkub3JnIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9"));
57+
assertEquals("eyJjaGFsbGVuZ2UiOiJBSUNRUzNyajZQLWRJRGI1aWYzT0N0ZS1ZN0NFc19CRW5wVGdvYXNRUlhnIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvcmlnaW4iOiJodHRwczovL3Bhc3NrZXkub3JnIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9", kpa.passkeysRegister(p, "https://passkey.org", l).getJSONObject("response").getString("clientDataJSON"));
58+
LOG.info("Please allow authenticate with the stored Passkey");
59+
publicKey = "{\"allowCredentials\":[],\"challenge\":\"8rRycwlx8ZOczHfALOJR-ef9RmYBmNt7HQABHxpcSvM\",\"rpId\":\"passkey.org\",\"timeout\":90000,\"userVerification\":\"preferred\"}";
60+
p = new JSONObject(publicKey);
61+
assertEquals("5Yaf4EYzO6ALp_K7s-p-BQLPSCYVYcKLZptoXwxqQzsFAAAAAA", kpa.passkeysGet(p, "https://passkey.org", l).getJSONObject("response").getString("authenticatorData"));
5862
LOG.info("Please deny to save changes");
5963
assertTrue(kpa.lockDatabase());
6064
assertTrue(kpa.shutdown());

0 commit comments

Comments
 (0)