Skip to content

Commit e60d332

Browse files
committed
Introduce interface ToPublicKeyCredentialDescriptor
1 parent 736226e commit e60d332

File tree

8 files changed

+113
-37
lines changed

8 files changed

+113
-37
lines changed

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.yubico.webauthn.data.AuthenticatorTransport;
44
import com.yubico.webauthn.data.ByteArray;
5+
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
56
import java.util.Optional;
67
import java.util.Set;
78
import lombok.NonNull;
@@ -12,7 +13,7 @@
1213
* before reaching a mature release.
1314
*/
1415
@Deprecated
15-
public interface CredentialRecord {
16+
public interface CredentialRecord extends ToPublicKeyCredentialDescriptor {
1617

1718
/**
1819
* @deprecated EXPERIMENTAL: This is an experimental feature. It is likely to change or be deleted
@@ -50,10 +51,7 @@ public interface CredentialRecord {
5051
* before reaching a mature release.
5152
*/
5253
@Deprecated
53-
@NonNull
54-
default Optional<Set<AuthenticatorTransport>> getTransports() {
55-
return Optional.empty();
56-
}
54+
Optional<Set<AuthenticatorTransport>> getTransports();
5755

5856
// boolean isUvInitialized();
5957

@@ -70,4 +68,24 @@ default Optional<Set<AuthenticatorTransport>> getTransports() {
7068
*/
7169
@Deprecated
7270
Optional<Boolean> isBackedUp();
71+
72+
/**
73+
* This default implementation of {@link
74+
* ToPublicKeyCredentialDescriptor#toPublicKeyCredentialDescriptor()} sets the {@link
75+
* PublicKeyCredentialDescriptor.PublicKeyCredentialDescriptorBuilder#id(ByteArray) id} field to
76+
* the return value of {@link #getCredentialId()} and the {@link
77+
* PublicKeyCredentialDescriptor.PublicKeyCredentialDescriptorBuilder#transports(Optional)
78+
* transports} field to the return value of {@link #getTransports()}.
79+
*
80+
* @see <a
81+
* href="https://w3c.github.io/webauthn/#credential-descriptor-for-a-credential-record">credential
82+
* descriptor for a credential record</a> in Web Authentication Level 3 (Editor's Draft)
83+
*/
84+
@Override
85+
default PublicKeyCredentialDescriptor toPublicKeyCredentialDescriptor() {
86+
return PublicKeyCredentialDescriptor.builder()
87+
.id(getCredentialId())
88+
.transports(getTransports())
89+
.build();
90+
}
7391
}

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

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

33
import com.yubico.webauthn.data.ByteArray;
4-
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
54
import java.util.Collections;
65
import java.util.Optional;
76
import java.util.Set;
@@ -14,7 +13,7 @@ class CredentialRepositoryV1ToV2Adapter
1413
private final CredentialRepository inner;
1514

1615
@Override
17-
public Set<PublicKeyCredentialDescriptor> getCredentialDescriptorsForUserHandle(
16+
public Set<? extends ToPublicKeyCredentialDescriptor> getCredentialDescriptorsForUserHandle(
1817
ByteArray userHandle) {
1918
return inner
2019
.getUsernameForUserHandle(userHandle)

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,24 @@ public interface CredentialRepositoryV2<C extends CredentialRecord> {
4848
* <p>After a successful registration ceremony, the {@link RegistrationResult#getKeyId()} method
4949
* returns a value suitable for inclusion in this set.
5050
*
51-
* @return a {@link Set} containing one {@link PublicKeyCredentialDescriptor} for each credential
52-
* registered to the given user. The set MUST NOT be null, but MAY be empty if the user does
53-
* not exist or has no credentials.
51+
* <p>Note that the {@link CredentialRecord} interface extends from the expected {@link
52+
* ToPublicKeyCredentialDescriptor} return type, so this method MAY return a {@link Set} of the
53+
* same item type as the value returned by the {@link #lookup(ByteArray, ByteArray)} method.
54+
*
55+
* <p>Implementations MUST NOT return null. The returned {@link Set} MUST NOT contain null.
56+
*
57+
* @return a {@link Set} containing one {@link PublicKeyCredentialDescriptor} (or value that
58+
* implements {@link ToPublicKeyCredentialDescriptor}, for example {@link CredentialRecord})
59+
* for each credential registered to the given user. The set MUST NOT be null, but MAY be
60+
* empty if the user does not exist or has no credentials.
61+
* @see ToPublicKeyCredentialDescriptor
62+
* @see CredentialRecord
5463
* @deprecated EXPERIMENTAL: This is an experimental feature. It is likely to change or be deleted
5564
* before reaching a mature release.
5665
*/
5766
@Deprecated
58-
Set<PublicKeyCredentialDescriptor> getCredentialDescriptorsForUserHandle(ByteArray userHandle);
67+
Set<? extends ToPublicKeyCredentialDescriptor> getCredentialDescriptorsForUserHandle(
68+
ByteArray userHandle);
5969

6070
/**
6171
* Look up the public key, backup flags and current signature count for the given credential

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@
4949
import java.security.SecureRandom;
5050
import java.security.Signature;
5151
import java.time.Clock;
52-
import java.util.ArrayList;
5352
import java.util.Arrays;
5453
import java.util.Collections;
5554
import java.util.List;
5655
import java.util.Optional;
5756
import java.util.Set;
57+
import java.util.stream.Collectors;
5858
import lombok.Builder;
5959
import lombok.NonNull;
6060
import lombok.Value;
@@ -431,8 +431,12 @@ public PublicKeyCredentialCreationOptions startRegistration(
431431
.challenge(generateChallenge())
432432
.pubKeyCredParams(preferredPubkeyParams)
433433
.excludeCredentials(
434-
credentialRepository.getCredentialDescriptorsForUserHandle(
435-
startRegistrationOptions.getUser().getId()))
434+
credentialRepository
435+
.getCredentialDescriptorsForUserHandle(
436+
startRegistrationOptions.getUser().getId())
437+
.stream()
438+
.map(ToPublicKeyCredentialDescriptor::toPublicKeyCredentialDescriptor)
439+
.collect(Collectors.toSet()))
436440
.authenticatorSelection(startRegistrationOptions.getAuthenticatorSelection())
437441
.extensions(
438442
startRegistrationOptions
@@ -488,7 +492,13 @@ public AssertionRequest startAssertion(StartAssertionOptions startAssertionOptio
488492
.getUsername()
489493
.flatMap(unr::getUserHandleForUsername)))
490494
.map(credentialRepository::getCredentialDescriptorsForUserHandle)
491-
.map(ArrayList::new))
495+
.map(
496+
descriptors ->
497+
descriptors.stream()
498+
.map(
499+
ToPublicKeyCredentialDescriptor
500+
::toPublicKeyCredentialDescriptor)
501+
.collect(Collectors.toList())))
492502
.extensions(
493503
startAssertionOptions
494504
.getExtensions()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.yubico.webauthn;
2+
3+
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
4+
5+
/**
6+
* A type that can be converted into a {@link PublicKeyCredentialDescriptor} value.
7+
*
8+
* @see PublicKeyCredentialDescriptor
9+
* @see <a
10+
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#dictdef-publickeycredentialdescriptor">§5.10.3.
11+
* Credential Descriptor (dictionary PublicKeyCredentialDescriptor)</a>
12+
* @see CredentialRecord
13+
* @deprecated EXPERIMENTAL: This is an experimental feature. It is likely to change or be deleted
14+
* before reaching a mature release.
15+
*/
16+
@Deprecated
17+
public interface ToPublicKeyCredentialDescriptor {
18+
19+
/**
20+
* Convert this value to a {@link PublicKeyCredentialDescriptor} value.
21+
*
22+
* <p>Implementations MUST NOT return null.
23+
*
24+
* @see PublicKeyCredentialDescriptor
25+
* @see <a
26+
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#dictdef-publickeycredentialdescriptor">§5.10.3.
27+
* Credential Descriptor (dictionary PublicKeyCredentialDescriptor)</a>
28+
* @see CredentialRecord
29+
* @deprecated EXPERIMENTAL: This is an experimental feature. It is likely to change or be deleted
30+
* before reaching a mature release.
31+
*/
32+
@Deprecated
33+
PublicKeyCredentialDescriptor toPublicKeyCredentialDescriptor();
34+
}

webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialDescriptor.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.yubico.internal.util.CollectionUtil;
3030
import com.yubico.internal.util.ComparableUtil;
3131
import com.yubico.webauthn.RegistrationResult;
32+
import com.yubico.webauthn.ToPublicKeyCredentialDescriptor;
3233
import java.util.Optional;
3334
import java.util.Set;
3435
import java.util.SortedSet;
@@ -49,7 +50,8 @@
4950
*/
5051
@Value
5152
@Builder(toBuilder = true)
52-
public class PublicKeyCredentialDescriptor implements Comparable<PublicKeyCredentialDescriptor> {
53+
public class PublicKeyCredentialDescriptor
54+
implements Comparable<PublicKeyCredentialDescriptor>, ToPublicKeyCredentialDescriptor {
5355

5456
/** The type of the credential the caller is referring to. */
5557
@NonNull @Builder.Default
@@ -108,6 +110,18 @@ public static PublicKeyCredentialDescriptorBuilder.MandatoryStages builder() {
108110
return new PublicKeyCredentialDescriptorBuilder.MandatoryStages();
109111
}
110112

113+
/**
114+
* This implementation of {@link
115+
* ToPublicKeyCredentialDescriptor#toPublicKeyCredentialDescriptor()} is a no-op which returns
116+
* <code>this</code> unchanged.
117+
*
118+
* @return <code>this</code>.
119+
*/
120+
@Override
121+
public PublicKeyCredentialDescriptor toPublicKeyCredentialDescriptor() {
122+
return this;
123+
}
124+
111125
public static class PublicKeyCredentialDescriptorBuilder {
112126
private Set<AuthenticatorTransport> transports = null;
113127

webauthn-server-core/src/test/scala/com/yubico/webauthn/test/Helpers.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.yubico.webauthn.CredentialRepositoryV2
66
import com.yubico.webauthn.RegisteredCredential
77
import com.yubico.webauthn.RegistrationResult
88
import com.yubico.webauthn.RegistrationTestData
9+
import com.yubico.webauthn.ToPublicKeyCredentialDescriptor
910
import com.yubico.webauthn.UsernameRepository
1011
import com.yubico.webauthn.data.AuthenticatorTransport
1112
import com.yubico.webauthn.data.ByteArray
@@ -141,7 +142,7 @@ object Helpers {
141142

142143
override def getCredentialDescriptorsForUserHandle(
143144
userHandle: ByteArray
144-
): java.util.Set[PublicKeyCredentialDescriptor] = {
145+
): java.util.Set[_ <: ToPublicKeyCredentialDescriptor] = {
145146
getCredentialIdsCount += 1
146147
inner.getCredentialDescriptorsForUserHandle(userHandle)
147148
}
@@ -166,19 +167,14 @@ object Helpers {
166167
new CredentialRepositoryV2[C] {
167168
override def getCredentialDescriptorsForUserHandle(
168169
userHandle: ByteArray
169-
): java.util.Set[PublicKeyCredentialDescriptor] =
170+
): java.util.Set[_ <: ToPublicKeyCredentialDescriptor] =
170171
users
171172
.filter({
172173
case (u, c) =>
173174
u.getId == userHandle && c.getUserHandle == userHandle
174175
})
175176
.map({
176-
case (_, credential) =>
177-
PublicKeyCredentialDescriptor
178-
.builder()
179-
.id(credential.getCredentialId)
180-
.transports(credential.getTransports)
181-
.build()
177+
case (_, credential) => credential
182178
})
183179
.toSet
184180
.asJava
@@ -305,6 +301,10 @@ object Helpers {
305301
override def getPublicKeyCose: ByteArray =
306302
testData.response.getResponse.getParsedAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey
307303
override def getSignatureCount: Long = signatureCount
304+
305+
override def getTransports
306+
: Optional[java.util.Set[AuthenticatorTransport]] =
307+
Optional.of(testData.response.getResponse.getTransports)
308308
override def isBackupEligible: Optional[java.lang.Boolean] = toJava(be)
309309
override def isBackedUp: Optional[java.lang.Boolean] = toJava(bs)
310310
}

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

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import com.yubico.webauthn.CredentialRepositoryV2;
3131
import com.yubico.webauthn.UsernameRepository;
3232
import com.yubico.webauthn.data.ByteArray;
33-
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
3433
import demo.webauthn.data.CredentialRegistration;
3534
import java.util.Collection;
3635
import java.util.HashSet;
@@ -56,16 +55,8 @@ public class InMemoryRegistrationStorage
5655
////////////////////////////////////////////////////////////////////////////////
5756

5857
@Override
59-
public Set<PublicKeyCredentialDescriptor> getCredentialDescriptorsForUserHandle(
60-
ByteArray userHandle) {
61-
return getRegistrationsByUserHandle(userHandle).stream()
62-
.map(
63-
registration ->
64-
PublicKeyCredentialDescriptor.builder()
65-
.id(registration.getCredential().getCredentialId())
66-
.transports(registration.getTransports())
67-
.build())
68-
.collect(Collectors.toSet());
58+
public Set<CredentialRegistration> getCredentialDescriptorsForUserHandle(ByteArray userHandle) {
59+
return getRegistrationsByUserHandle(userHandle);
6960
}
7061

7162
@Override
@@ -135,13 +126,13 @@ public Collection<CredentialRegistration> getRegistrationsByUsername(String user
135126
}
136127
}
137128

138-
public Collection<CredentialRegistration> getRegistrationsByUserHandle(ByteArray userHandle) {
129+
public Set<CredentialRegistration> getRegistrationsByUserHandle(ByteArray userHandle) {
139130
return storage.asMap().values().stream()
140131
.flatMap(Collection::stream)
141132
.filter(
142133
credentialRegistration ->
143134
userHandle.equals(credentialRegistration.getUserIdentity().getId()))
144-
.collect(Collectors.toList());
135+
.collect(Collectors.toSet());
145136
}
146137

147138
public void updateSignatureCount(AssertionResultV2<CredentialRegistration> result) {

0 commit comments

Comments
 (0)