Skip to content

Commit 678bebf

Browse files
authored
add WebAuthn Challenge Object (#17)
Added parsing for triggered webauthn challenges
1 parent 193f16c commit 678bebf

File tree

3 files changed

+205
-19
lines changed

3 files changed

+205
-19
lines changed

src/main/java/org/privacyidea/PIResponse.java

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,39 @@ public PIResponse(String json) {
6464
JsonElement detailElem = obj.get("detail");
6565
if (detailElem != null && !detailElem.isJsonNull()) {
6666
JsonObject detail = obj.getAsJsonObject("detail");
67-
if (detail != null) {
68-
this.message = getString(detail, "message");
69-
this.serial = getString(detail, "serial");
70-
this.transaction_id = getString(detail, "transaction_id");
71-
this.type = getString(detail, "type");
72-
this.otplen = getInt(detail, "otplen");
73-
74-
JsonArray arrMessages = detail.getAsJsonArray("messages");
75-
if (arrMessages != null) {
76-
arrMessages.forEach(val -> {
77-
if (val != null) {
78-
this.messages.add(val.getAsString());
79-
}
80-
});
81-
}
67+
this.message = getString(detail, "message");
68+
this.serial = getString(detail, "serial");
69+
this.transaction_id = getString(detail, "transaction_id");
70+
this.type = getString(detail, "type");
71+
this.otplen = getInt(detail, "otplen");
72+
73+
74+
JsonArray arrMessages = detail.getAsJsonArray("messages");
75+
if (arrMessages != null) {
76+
arrMessages.forEach(val -> {
77+
if (val != null) {
78+
this.messages.add(val.getAsString());
79+
}
80+
});
81+
}
8282

83-
JsonArray arrChallenges = detail.getAsJsonArray("multi_challenge");
84-
if (arrChallenges != null) {
85-
for (int i = 0; i < arrChallenges.size(); i++) {
86-
JsonObject challenge = arrChallenges.get(i).getAsJsonObject();
83+
JsonArray arrChallenges = detail.getAsJsonArray("multi_challenge");
84+
if (arrChallenges != null) {
85+
for (int i = 0; i < arrChallenges.size(); i++) {
86+
JsonObject challenge = arrChallenges.get(i).getAsJsonObject();
87+
if (getString(challenge, "type").equals("webauthn")) {
88+
JsonObject attrObj = challenge.getAsJsonObject("attributes");
89+
if (attrObj != null && !attrObj.isJsonNull()) {
90+
JsonObject webauthnObj = attrObj.getAsJsonObject("webAuthnSignRequest");
91+
multichallenge.add(new WebAuthn(
92+
getString(challenge, "serial"),
93+
getString(challenge, "message"),
94+
getString(challenge, "transaction_id"),
95+
"webauthn",
96+
webauthnObj.toString()
97+
));
98+
}
99+
} else {
87100
multichallenge.add(new Challenge(
88101
getString(challenge, "serial"),
89102
getString(challenge, "message"),
@@ -93,6 +106,7 @@ public PIResponse(String json) {
93106
}
94107
}
95108
}
109+
96110
}
97111
}
98112

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.privacyidea;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
public class WebAuthn extends Challenge{
7+
8+
private final List<String> attributes = new ArrayList<>();
9+
private final String webAuthn;
10+
11+
public WebAuthn(String serial, String message, String transaction_id, String type, String webAuthn) {
12+
super(serial, message, transaction_id, type);
13+
this.webAuthn = webAuthn;
14+
}
15+
16+
public List<String> getAttributes() {
17+
return attributes;
18+
}
19+
20+
21+
public String getWebAuthn() {
22+
return webAuthn;
23+
}
24+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package org.privacyidea;
2+
3+
import org.junit.Before;
4+
import org.junit.Test;
5+
import org.mockserver.integration.ClientAndServer;
6+
import org.mockserver.model.HttpRequest;
7+
import org.mockserver.model.HttpResponse;
8+
9+
import java.util.Optional;
10+
11+
import static org.junit.Assert.*;
12+
13+
public class TestWebAuthn implements PILoggerBridge {
14+
private ClientAndServer mockServer;
15+
private PrivacyIDEA privacyIDEA;
16+
17+
18+
@Before
19+
public void setup() {
20+
mockServer = ClientAndServer.startClientAndServer(1080);
21+
22+
privacyIDEA = new PrivacyIDEA.Builder("https://127.0.0.1:1080", "test")
23+
.setSSLVerify(false)
24+
.setLogger(this)
25+
.build();
26+
}
27+
28+
@Test
29+
public void test() {
30+
String webauthnrequest = "{\n" +
31+
" \"allowCredentials\": [\n" +
32+
" {\n" +
33+
" \"id\": \"83De8z_CNqogB6aCyKs6dWIqwpOpzVoNaJ74lgcpuYN7l-95QsD3z-qqPADqsFlPwBXCMqEPssq75kqHCMQHDA\",\n" +
34+
" \"transports\": [\n" +
35+
" \"internal\",\n" +
36+
" \"nfc\",\n" +
37+
" \"ble\",\n" +
38+
" \"usb\"\n" +
39+
" ],\n" +
40+
" \"type\": \"public-key\"\n" +
41+
" }\n" +
42+
" ],\n" +
43+
" \"challenge\": \"dHzSmZnAhxEq0szRWMY4EGg8qgjeBhJDjAPYKWfd2IE\",\n" +
44+
" \"rpId\": \"office.netknights.it\",\n" +
45+
" \"timeout\": 60000,\n" +
46+
" \"userVerification\": \"preferred\"\n" +
47+
" }\n";
48+
mockServer.when(
49+
HttpRequest.request()
50+
.withPath(Constants.ENDPOINT_VALIDATE_CHECK)
51+
.withMethod("POST")
52+
.withBody("user=Test&pass=Test"))
53+
.respond(HttpResponse.response()
54+
// This response is simplified because it is very long and contains info that is not (yet) processed anyway
55+
.withBody("{\n" +
56+
" \"detail\": {\n" +
57+
" \"attributes\": {\n" +
58+
" \"hideResponseInput\": true,\n" +
59+
" \"img\": \"static/img/FIDO-U2F-Security-Key-444x444.png\",\n" +
60+
" \"webAuthnSignRequest\": {\n" +
61+
" \"allowCredentials\": [\n" +
62+
" {\n" +
63+
" \"id\": \"83De8z_CNqogB6aCyKs6dWIqwpOpzVoNaJ74lgcpuYN7l-95QsD3z-qqPADqsFlPwBXCMqEPssq75kqHCMQHDA\",\n" +
64+
" \"transports\": [\n" +
65+
" \"internal\",\n" +
66+
" \"nfc\",\n" +
67+
" \"ble\",\n" +
68+
" \"usb\"\n" +
69+
" ],\n" +
70+
" \"type\": \"public-key\"\n" +
71+
" }\n" +
72+
" ],\n" +
73+
" \"challenge\": \"dHzSmZnAhxEq0szRWMY4EGg8qgjeBhJDjAPYKWfd2IE\",\n" +
74+
" \"rpId\": \"office.netknights.it\",\n" +
75+
" \"timeout\": 60000,\n" +
76+
" \"userVerification\": \"preferred\"\n" +
77+
" }\n" +
78+
" },\n" +
79+
" \"message\": \"Please confirm with your WebAuthn token (Yubico U2F EE Serial 61730834)\",\n" +
80+
" \"messages\": [\n" +
81+
" \"Please confirm with your WebAuthn token (Yubico U2F EE Serial 61730834)\"\n" +
82+
" ],\n" +
83+
" \"multi_challenge\": [\n" +
84+
" {\n" +
85+
" \"attributes\": {\n" +
86+
" \"hideResponseInput\": true,\n" +
87+
" \"img\": \"static/img/FIDO-U2F-Security-Key-444x444.png\",\n" +
88+
" \"webAuthnSignRequest\": " + webauthnrequest +
89+
" },\n" +
90+
" \"message\": \"Please confirm with your WebAuthn token (Yubico U2F EE Serial 61730834)\",\n" +
91+
" \"serial\": \"WAN00025CE7\",\n" +
92+
" \"transaction_id\": \"16786665691788289392\",\n" +
93+
" \"type\": \"webauthn\"\n" +
94+
" }\n" +
95+
" ],\n" +
96+
" \"serial\": \"WAN00025CE7\",\n" +
97+
" \"threadid\": 140040275289856,\n" +
98+
" \"transaction_id\": \"16786665691788289392\",\n" +
99+
" \"transaction_ids\": [\n" +
100+
" \"16786665691788289392\"\n" +
101+
" ],\n" +
102+
" \"type\": \"webauthn\"\n" +
103+
" },\n" +
104+
" \"id\": 1,\n" +
105+
" \"jsonrpc\": \"2.0\",\n" +
106+
" \"result\": {\n" +
107+
" \"status\": true,\n" +
108+
" \"value\": false\n" +
109+
" },\n" +
110+
" \"time\": 1611916339.8448942,\n" +
111+
" \"version\": \"privacyIDEA 3.5\",\n" +
112+
" \"versionnumber\": \"3.5\",\n" +
113+
" \"signature\": \"rsa_sha256_pss:0046a8c82b9063d7c9e78bac8f4accd8e1645493ced5cbf0db7a1eecdec1610b56dacb5ed12c4a6d729fbe496a4240053ab02dd2dafa407ab3b3dbd7f2dd1aeb19b6fb7a0a67a303d55d2081ff39258ed2579317601f3e09c7a2588cce7f85d15ab8b347b44c3810164a21542439f72aa2130e1cdbb1bdbc58e0aed1d8e8a265e5193601246969bb50b9d7b3486d75ca4844902e0dff80b52f370037981ac2210f405db0bc901e6333391f638a8b9315d0e34e7c56af0496b79fac25d4a8623788735dce8d450e40f4f68018883c8d81065a8492dc9894a6fbd025a199dc9a9c9f08efd7ade34ba163727a5f516ef512a14258e88c0d10bdc6c090cf62740c2b\"\n" +
114+
"}\n" +
115+
""));
116+
PIResponse response = privacyIDEA.validateCheck("Test","Test");
117+
118+
Optional <Challenge> opt = response.getMultiChallenge().stream().filter(challenge -> challenge.getType().equals("webauthn")).findFirst();
119+
assertTrue(opt.isPresent());
120+
Challenge a = opt.get();
121+
if (a instanceof WebAuthn) {
122+
WebAuthn b = (WebAuthn) a;
123+
assertEquals(webauthnrequest.replaceAll("\n","").replaceAll(" ",""),b.getWebAuthn());
124+
} else {
125+
fail();
126+
}
127+
}
128+
129+
@Override
130+
public void log(String message) {
131+
132+
}
133+
134+
@Override
135+
public void error(String message) {
136+
137+
}
138+
139+
@Override
140+
public void log(Throwable t) {
141+
142+
}
143+
144+
@Override
145+
public void error(Throwable t) {
146+
147+
}
148+
}

0 commit comments

Comments
 (0)