Skip to content

Commit 17b7bb3

Browse files
authored
Merge pull request #280 from Yubico/demo-fido-mds
Enable using FidoMetadataService in demo
2 parents fddc63e + 6ac5333 commit 17b7bb3

File tree

11 files changed

+235
-90
lines changed

11 files changed

+235
-90
lines changed

webauthn-server-demo/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# FidoMetadataDownloader cache files
2+
webauthn-server-demo-fido-mds-blob-cache.bin
3+
webauthn-server-demo-fido-mds-trust-root-cache.bin

webauthn-server-demo/README

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ class which provides the REST API on top of it.
1818
$ ./gradlew run
1919
$ $BROWSER https://localhost:8443/
2020

21+
Or to run with the https://fidoalliance.org/metadata/[FIDO Metadata Service] as
22+
a source of attestation metadata:
23+
24+
$ YUBICO_WEBAUTHN_USE_FIDO_MDS=true ./gradlew run
25+
$ $BROWSER https://localhost:8443/
26+
2127

2228
== Architecture
2329

@@ -150,3 +156,11 @@ correct environment.
150156
https://www.w3.org/TR/webauthn/#dom-publickeycredentialentity-name[RP name]
151157
the server will report. Example: `YUBICO_WEBAUTHN_RP_ID='Yubico Web
152158
Authentication demo'`
159+
160+
- `YUBICO_WEBAUTHN_USE_FIDO_MDS`: If set to `true` (case-insensitive), use
161+
https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.4.1/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`]
162+
from the link:../webauthn-server-attestation[`webauthn-server-attestation`]
163+
module as a source of attestation data in addition to the static JSON file
164+
bundled with the demo. This will write cache files to the
165+
`webauthn-server-demo` project root directory; see the patterns in the
166+
`.gitignore` file.

webauthn-server-demo/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ dependencies {
5555

5656
application {
5757
mainClass.set("demo.webauthn.EmbeddedServer")
58+
59+
// Required for processing CRL distribution points extension
60+
applicationDefaultJvmArgs = listOf("-Dcom.sun.security.enableCRLDP=true")
5861
}
5962

6063
for (task in listOf(tasks.installDist, tasks.distZip, tasks.distTar)) {

webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/YubicoJsonMetadataService.java

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
import com.yubico.internal.util.CertificateParser;
3232
import com.yubico.internal.util.CollectionUtil;
3333
import com.yubico.internal.util.OptionalUtil;
34+
import com.yubico.webauthn.RegistrationResult;
3435
import com.yubico.webauthn.attestation.matcher.ExtensionMatcher;
3536
import com.yubico.webauthn.data.ByteArray;
37+
import demo.webauthn.MetadataService;
3638
import java.security.cert.CertificateException;
3739
import java.security.cert.X509Certificate;
3840
import java.util.Collection;
@@ -48,7 +50,7 @@
4850
import lombok.extern.slf4j.Slf4j;
4951

5052
@Slf4j
51-
public final class YubicoJsonMetadataService implements AttestationTrustSource {
53+
public final class YubicoJsonMetadataService implements AttestationTrustSource, MetadataService {
5254

5355
private static final String SELECTORS = "selectors";
5456
private static final String SELECTOR_TYPE = "type";
@@ -90,43 +92,53 @@ public YubicoJsonMetadataService() {
9092
DEFAULT_DEVICE_MATCHERS);
9193
}
9294

93-
public Optional<Attestation> findMetadata(X509Certificate attestationCertificate) {
94-
return metadataObjects.stream()
95+
@Override
96+
public Set<Object> findEntries(@NonNull RegistrationResult registrationResult) {
97+
return registrationResult
98+
.getAttestationTrustPath()
9599
.map(
96-
metadata -> {
97-
Map<String, String> vendorProperties;
98-
Map<String, String> deviceProperties = null;
99-
String identifier;
100+
certs -> {
101+
X509Certificate attestationCertificate = certs.get(0);
102+
return metadataObjects.stream()
103+
.map(
104+
metadata -> {
105+
Map<String, String> vendorProperties;
106+
Map<String, String> deviceProperties = null;
107+
String identifier;
100108

101-
identifier = metadata.getIdentifier();
102-
vendorProperties = Maps.filterValues(metadata.getVendorInfo(), Objects::nonNull);
103-
for (JsonNode device : metadata.getDevices()) {
104-
if (deviceMatches(device.get(SELECTORS), attestationCertificate)) {
105-
ImmutableMap.Builder<String, String> devicePropertiesBuilder =
106-
ImmutableMap.builder();
107-
for (Map.Entry<String, JsonNode> deviceEntry :
108-
Lists.newArrayList(device.fields())) {
109-
JsonNode value = deviceEntry.getValue();
110-
if (value.isTextual()) {
111-
devicePropertiesBuilder.put(deviceEntry.getKey(), value.asText());
112-
}
113-
}
114-
deviceProperties = devicePropertiesBuilder.build();
115-
break;
116-
}
117-
}
109+
identifier = metadata.getIdentifier();
110+
vendorProperties =
111+
Maps.filterValues(metadata.getVendorInfo(), Objects::nonNull);
112+
for (JsonNode device : metadata.getDevices()) {
113+
if (deviceMatches(device.get(SELECTORS), attestationCertificate)) {
114+
ImmutableMap.Builder<String, String> devicePropertiesBuilder =
115+
ImmutableMap.builder();
116+
for (Map.Entry<String, JsonNode> deviceEntry :
117+
Lists.newArrayList(device.fields())) {
118+
JsonNode value = deviceEntry.getValue();
119+
if (value.isTextual()) {
120+
devicePropertiesBuilder.put(deviceEntry.getKey(), value.asText());
121+
}
122+
}
123+
deviceProperties = devicePropertiesBuilder.build();
124+
break;
125+
}
126+
}
118127

119-
return Optional.ofNullable(deviceProperties)
120-
.map(
121-
deviceProps ->
122-
Attestation.builder()
123-
.metadataIdentifier(Optional.ofNullable(identifier))
124-
.vendorProperties(Optional.of(vendorProperties))
125-
.deviceProperties(deviceProps)
126-
.build());
128+
return Optional.ofNullable(deviceProperties)
129+
.map(
130+
deviceProps ->
131+
Attestation.builder()
132+
.metadataIdentifier(Optional.ofNullable(identifier))
133+
.vendorProperties(Optional.of(vendorProperties))
134+
.deviceProperties(deviceProps)
135+
.build());
136+
})
137+
.flatMap(OptionalUtil::stream)
138+
.map(attestation -> (Object) attestation)
139+
.collect(Collectors.toSet());
127140
})
128-
.flatMap(OptionalUtil::stream)
129-
.findAny();
141+
.orElseGet(Collections::emptySet);
130142
}
131143

132144
private boolean deviceMatches(
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package demo.webauthn;
2+
3+
import com.yubico.webauthn.RegistrationResult;
4+
import com.yubico.webauthn.attestation.AttestationTrustSource;
5+
import com.yubico.webauthn.data.ByteArray;
6+
import java.security.cert.X509Certificate;
7+
import java.util.*;
8+
import lombok.NonNull;
9+
10+
/**
11+
* Combines several attestation metadata sources into one, which delegates to each sub-service in
12+
* order until one returns a non-empty result.
13+
*/
14+
public class CompositeMetadataService implements AttestationTrustSource, MetadataService {
15+
16+
private final List<MetadataService> delegates;
17+
18+
public CompositeMetadataService(MetadataService... delegates) {
19+
this.delegates = Collections.unmodifiableList(Arrays.asList(delegates));
20+
}
21+
22+
@Override
23+
public TrustRootsResult findTrustRoots(
24+
List<X509Certificate> attestationCertificateChain, Optional<ByteArray> aaguid) {
25+
for (MetadataService delegate : delegates) {
26+
TrustRootsResult res = delegate.findTrustRoots(attestationCertificateChain, aaguid);
27+
if (!res.getTrustRoots().isEmpty()) {
28+
return res;
29+
}
30+
}
31+
32+
return TrustRootsResult.builder().trustRoots(Collections.emptySet()).build();
33+
}
34+
35+
@Override
36+
public Set<Object> findEntries(@NonNull RegistrationResult registrationResult) {
37+
for (MetadataService delegate : delegates) {
38+
Set<Object> res = delegate.findEntries(registrationResult);
39+
if (!res.isEmpty()) {
40+
return res;
41+
}
42+
}
43+
44+
return Collections.emptySet();
45+
}
46+
}

webauthn-server-demo/src/main/java/demo/webauthn/Config.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ public static RelyingPartyIdentity getRpIdentity() {
7878
return getInstance().rpIdentity;
7979
}
8080

81+
public static boolean useFidoMds() {
82+
return "true".equalsIgnoreCase(System.getenv("YUBICO_WEBAUTHN_USE_FIDO_MDS"));
83+
}
84+
8185
private static Set<String> computeOrigins() {
8286
final String origins = System.getenv("YUBICO_WEBAUTHN_ALLOWED_ORIGINS");
8387

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package demo.webauthn;
2+
3+
import com.yubico.fido.metadata.FidoMetadataService;
4+
import com.yubico.webauthn.RegistrationResult;
5+
import com.yubico.webauthn.data.ByteArray;
6+
import java.security.cert.X509Certificate;
7+
import java.util.List;
8+
import java.util.Optional;
9+
import java.util.Set;
10+
import java.util.stream.Collectors;
11+
import lombok.AllArgsConstructor;
12+
import lombok.NonNull;
13+
14+
@AllArgsConstructor
15+
public class FidoMetadataServiceAdapter implements MetadataService {
16+
private final FidoMetadataService fido;
17+
18+
@Override
19+
public TrustRootsResult findTrustRoots(
20+
List<X509Certificate> attestationCertificateChain, Optional<ByteArray> aaguid) {
21+
return fido.findTrustRoots(attestationCertificateChain, aaguid);
22+
}
23+
24+
@Override
25+
public Set<Object> findEntries(@NonNull RegistrationResult registrationResult) {
26+
return fido.findEntries(registrationResult).stream()
27+
.map(metadataBLOBPayloadEntry -> (Object) metadataBLOBPayloadEntry)
28+
.collect(Collectors.toSet());
29+
}
30+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package demo.webauthn;
2+
3+
import com.yubico.webauthn.RegistrationResult;
4+
import com.yubico.webauthn.attestation.AttestationTrustSource;
5+
import java.util.Set;
6+
import lombok.NonNull;
7+
8+
public interface MetadataService extends AttestationTrustSource {
9+
Set<Object> findEntries(@NonNull RegistrationResult registrationResult);
10+
}

0 commit comments

Comments
 (0)