Skip to content

Commit da220fe

Browse files
committed
Implement appid extension
2 parents d4d84ec + ed82ee0 commit da220fe

File tree

46 files changed

+980
-370
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+980
-370
lines changed

webauthn-server-core/src/main/java/com/yubico/webauthn/ExtensionsValidation.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package com.yubico.webauthn;
22

3-
import com.fasterxml.jackson.databind.node.ObjectNode;
43
import com.upokecenter.cbor.CBORObject;
5-
import com.yubico.internal.util.ExceptionUtil;
6-
import com.yubico.internal.util.StreamUtil;
74
import com.yubico.webauthn.data.AuthenticatorResponse;
5+
import com.yubico.webauthn.data.ClientExtensionOutputs;
6+
import com.yubico.webauthn.data.ExtensionInputs;
87
import com.yubico.webauthn.data.PublicKeyCredential;
98
import java.util.HashSet;
10-
import java.util.Optional;
119
import java.util.Set;
1210
import java.util.stream.Collectors;
1311
import lombok.experimental.UtilityClass;
@@ -16,9 +14,9 @@
1614
@UtilityClass
1715
class ExtensionsValidation {
1816

19-
static boolean validate(Optional<ObjectNode> requested, PublicKeyCredential<? extends AuthenticatorResponse> response) {
20-
Set<String> requestedExtensionIds = requested.map(req -> StreamUtil.toSet(req.fieldNames())).orElseGet(HashSet::new);
21-
Set<String> clientExtensionIds = StreamUtil.toSet(response.getClientExtensionResults().fieldNames());
17+
static boolean validate(ExtensionInputs requested, PublicKeyCredential<? extends AuthenticatorResponse, ? extends ClientExtensionOutputs> response) {
18+
Set<String> requestedExtensionIds = requested.getExtensionIds();
19+
Set<String> clientExtensionIds = response.getClientExtensionResults().getExtensionIds();
2220

2321
if (!requestedExtensionIds.containsAll(clientExtensionIds)) {
2422
throw new IllegalArgumentException(String.format(

webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionOptions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.yubico.webauthn.data.AssertionRequest;
44
import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
55
import com.yubico.webauthn.data.ByteArray;
6+
import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
67
import com.yubico.webauthn.data.PublicKeyCredential;
78
import java.util.Optional;
89
import lombok.Builder;
@@ -16,7 +17,7 @@ public class FinishAssertionOptions {
1617
@NonNull
1718
private final AssertionRequest request;
1819
@NonNull
19-
private final PublicKeyCredential<AuthenticatorAssertionResponse> response;
20+
private final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> response;
2021

2122
@NonNull
2223
@Builder.Default

webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import com.yubico.webauthn.data.AssertionResult;
66
import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
77
import com.yubico.webauthn.data.ByteArray;
8+
import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
89
import com.yubico.webauthn.data.CollectedClientData;
910
import com.yubico.webauthn.data.PublicKeyCredential;
1011
import com.yubico.webauthn.data.UserVerificationRequirement;
12+
import com.yubico.webauthn.extension.appid.AppId;
1113
import java.util.ArrayList;
1214
import java.util.Collections;
1315
import java.util.LinkedList;
@@ -27,7 +29,7 @@ class FinishAssertionSteps {
2729
private static final String CLIENT_DATA_TYPE = "webauthn.get";
2830

2931
private final AssertionRequest request;
30-
private final PublicKeyCredential<AuthenticatorAssertionResponse> response;
32+
private final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> response;
3133
private final Optional<ByteArray> callerTokenBindingId;
3234
private final List<String> origins;
3335
private final String rpId;
@@ -397,10 +399,22 @@ public class Step11 implements Step<Step10, Step12> {
397399

398400
@Override
399401
public void validate() {
400-
assure(
401-
crypto.hash(rpId).equals(response.getResponse().getParsedAuthenticatorData().getRpIdHash()),
402-
"Wrong RP ID hash."
403-
);
402+
try {
403+
assure(
404+
crypto.hash(rpId).equals(response.getResponse().getParsedAuthenticatorData().getRpIdHash()),
405+
"Wrong RP ID hash."
406+
);
407+
} catch (IllegalArgumentException e) {
408+
Optional<AppId> appid = request.getPublicKeyCredentialRequestOptions().getExtensions().getAppid();
409+
if (appid.isPresent()) {
410+
assure(
411+
crypto.hash(appid.get().getId()).equals(response.getResponse().getParsedAuthenticatorData().getRpIdHash()),
412+
"Wrong RP ID hash."
413+
);
414+
} else {
415+
throw e;
416+
}
417+
}
404418
}
405419

406420
@Override

webauthn-server-core/src/main/java/com/yubico/webauthn/FinishRegistrationOptions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
44
import com.yubico.webauthn.data.ByteArray;
5+
import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
56
import com.yubico.webauthn.data.PublicKeyCredential;
67
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
78
import java.util.Optional;
@@ -16,7 +17,7 @@ public class FinishRegistrationOptions {
1617
@NonNull
1718
private final PublicKeyCredentialCreationOptions request;
1819
@NonNull
19-
private final PublicKeyCredential<AuthenticatorAttestationResponse> response;
20+
private final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> response;
2021

2122
@NonNull
2223
@Builder.Default

webauthn-server-core/src/main/java/com/yubico/webauthn/FinishRegistrationSteps.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
99
import com.yubico.webauthn.data.AuthenticatorSelectionCriteria;
1010
import com.yubico.webauthn.data.ByteArray;
11+
import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
1112
import com.yubico.webauthn.data.CollectedClientData;
1213
import com.yubico.webauthn.data.PublicKeyCredential;
1314
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
@@ -36,7 +37,7 @@ class FinishRegistrationSteps {
3637
private static final String CLIENT_DATA_TYPE = "webauthn.create";
3738

3839
private final PublicKeyCredentialCreationOptions request;
39-
private final PublicKeyCredential<AuthenticatorAttestationResponse> response;
40+
private final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> response;
4041
private final Optional<ByteArray> callerTokenBindingId;
4142
private final List<String> origins;
4243
private final String rpId;

webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingParty.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
99
import com.yubico.webauthn.data.AuthenticatorSelectionCriteria;
1010
import com.yubico.webauthn.data.ByteArray;
11+
import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
12+
import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
1113
import com.yubico.webauthn.data.PublicKeyCredential;
1214
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
1315
import com.yubico.webauthn.data.PublicKeyCredentialParameters;
@@ -16,6 +18,7 @@
1618
import com.yubico.webauthn.data.RelyingPartyIdentity;
1719
import com.yubico.webauthn.exception.AssertionFailedException;
1820
import com.yubico.webauthn.exception.RegistrationFailedException;
21+
import com.yubico.webauthn.extension.appid.AppId;
1922
import java.util.ArrayList;
2023
import java.util.List;
2124
import java.util.Optional;
@@ -35,6 +38,8 @@ public class RelyingParty {
3538
private final List<String> origins;
3639
private final CredentialRepository credentialRepository;
3740

41+
@Builder.Default
42+
private final Optional<AppId> appId = Optional.empty();
3843
@Builder.Default
3944
private final ChallengeGenerator challengeGenerator = new RandomChallengeGenerator();
4045
@Builder.Default
@@ -91,7 +96,7 @@ public RegistrationResult finishRegistration(FinishRegistrationOptions finishReg
9196
*/
9297
FinishRegistrationSteps _finishRegistration(
9398
PublicKeyCredentialCreationOptions request,
94-
PublicKeyCredential<AuthenticatorAttestationResponse> response,
99+
PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> response,
95100
Optional<ByteArray> callerTokenBindingId
96101
) {
97102
return FinishRegistrationSteps.builder()
@@ -120,7 +125,12 @@ public AssertionRequest startAssertion(StartAssertionOptions startAssertionOptio
120125
startAssertionOptions.getUsername().map(un ->
121126
new ArrayList<>(credentialRepository.getCredentialIdsForUsername(un)))
122127
)
123-
.extensions(startAssertionOptions.getExtensions())
128+
.extensions(
129+
startAssertionOptions.getExtensions()
130+
.toBuilder()
131+
.appid(appId)
132+
.build()
133+
)
124134
.build()
125135
)
126136
.build();
@@ -144,7 +154,7 @@ public AssertionResult finishAssertion(FinishAssertionOptions finishAssertionOpt
144154
*/
145155
FinishAssertionSteps _finishAssertion(
146156
AssertionRequest request,
147-
PublicKeyCredential<AuthenticatorAssertionResponse> response,
157+
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> response,
148158
Optional<ByteArray> callerTokenBindingId // = None.asJava
149159
) {
150160
return FinishAssertionSteps.builder()

webauthn-server-core/src/main/java/com/yubico/webauthn/StartAssertionOptions.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.yubico.webauthn;
22

3-
import com.fasterxml.jackson.databind.node.ObjectNode;
3+
import com.yubico.webauthn.data.AssertionExtensionInputs;
44
import java.util.Optional;
55
import lombok.Builder;
66
import lombok.NonNull;
@@ -16,7 +16,7 @@ public class StartAssertionOptions {
1616

1717
@NonNull
1818
@Builder.Default
19-
private final Optional<ObjectNode> extensions = Optional.empty();
19+
private final AssertionExtensionInputs extensions = AssertionExtensionInputs.builder().build();
2020

2121

2222
}

webauthn-server-core/src/main/java/com/yubico/webauthn/StartRegistrationOptions.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package com.yubico.webauthn;
22

3-
import com.fasterxml.jackson.databind.node.ObjectNode;
3+
import com.yubico.webauthn.data.RegistrationExtensionInputs;
44
import com.yubico.webauthn.data.UserIdentity;
5-
import java.util.Optional;
65
import lombok.Builder;
76
import lombok.NonNull;
87
import lombok.Value;
@@ -16,7 +15,7 @@ public class StartRegistrationOptions {
1615

1716
@NonNull
1817
@Builder.Default
19-
private final Optional<ObjectNode> extensions = Optional.empty();
18+
private final RegistrationExtensionInputs extensions = RegistrationExtensionInputs.builder().build();
2019

2120
@Builder.Default
2221
private final boolean requireResidentKey = false;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.yubico.webauthn.data;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonIgnore;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import com.yubico.webauthn.extension.appid.AppId;
7+
import java.util.HashSet;
8+
import java.util.Optional;
9+
import java.util.Set;
10+
import lombok.Builder;
11+
import lombok.NonNull;
12+
import lombok.Value;
13+
14+
@Value
15+
@Builder(toBuilder = true)
16+
public class AssertionExtensionInputs implements ExtensionInputs {
17+
18+
@Builder.Default
19+
private final Optional<AppId> appid = Optional.empty();
20+
21+
@JsonCreator
22+
private AssertionExtensionInputs(
23+
@NonNull @JsonProperty("appid") Optional<AppId> appid
24+
) {
25+
this.appid = appid;
26+
}
27+
28+
@Override
29+
@JsonIgnore
30+
public Set<String> getExtensionIds() {
31+
Set<String> ids = new HashSet<>();
32+
33+
appid.ifPresent((id) -> ids.add("appid"));
34+
35+
return ids;
36+
}
37+
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.yubico.webauthn.data;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonIgnore;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import java.util.Collections;
7+
import java.util.HashSet;
8+
import java.util.Optional;
9+
import java.util.Set;
10+
import lombok.Builder;
11+
import lombok.NonNull;
12+
import lombok.Value;
13+
14+
@Value
15+
@Builder
16+
public class ClientAssertionExtensionOutputs implements ClientExtensionOutputs {
17+
18+
@Builder.Default
19+
private final Optional<Boolean> appid = Optional.empty();
20+
21+
@JsonCreator
22+
private ClientAssertionExtensionOutputs(
23+
@NonNull @JsonProperty("appid") Optional<Boolean> appid
24+
) {
25+
this.appid = appid;
26+
}
27+
28+
@Override
29+
@JsonIgnore
30+
public Set<String> getExtensionIds() {
31+
Set<String> ids = new HashSet<>();
32+
33+
appid.ifPresent((id) -> ids.add("appid"));
34+
35+
return ids;
36+
}
37+
38+
}

0 commit comments

Comments
 (0)