Skip to content

Commit ec0841a

Browse files
committed
Introduce alternative CredentialRepositoryV2 interface and related types
1 parent b62ee43 commit ec0841a

15 files changed

+1491
-245
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Copyright (c) 2018, Yubico AB
2+
// All rights reserved.
3+
//
4+
// Redistribution and use in source and binary forms, with or without
5+
// modification, are permitted provided that the following conditions are met:
6+
//
7+
// 1. Redistributions of source code must retain the above copyright notice, this
8+
// list of conditions and the following disclaimer.
9+
//
10+
// 2. Redistributions in binary form must reproduce the above copyright notice,
11+
// this list of conditions and the following disclaimer in the documentation
12+
// and/or other materials provided with the distribution.
13+
//
14+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17+
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20+
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22+
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24+
25+
package com.yubico.webauthn;
26+
27+
import com.fasterxml.jackson.annotation.JsonCreator;
28+
import com.fasterxml.jackson.annotation.JsonIgnore;
29+
import com.fasterxml.jackson.annotation.JsonProperty;
30+
import com.yubico.webauthn.data.AuthenticatorAssertionExtensionOutputs;
31+
import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
32+
import com.yubico.webauthn.data.AuthenticatorAttachment;
33+
import com.yubico.webauthn.data.AuthenticatorData;
34+
import com.yubico.webauthn.data.AuthenticatorDataFlags;
35+
import com.yubico.webauthn.data.AuthenticatorResponse;
36+
import com.yubico.webauthn.data.ByteArray;
37+
import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
38+
import com.yubico.webauthn.data.PublicKeyCredential;
39+
import java.util.Optional;
40+
import lombok.AccessLevel;
41+
import lombok.Getter;
42+
import lombok.NonNull;
43+
import lombok.Value;
44+
45+
/** The result of a call to {@link RelyingPartyV2#finishAssertion(FinishAssertionOptions)}. */
46+
@Value
47+
public class AssertionResultV2<C extends CredentialRecord> {
48+
49+
/** <code>true</code> if the assertion was verified successfully. */
50+
private final boolean success;
51+
52+
@JsonProperty
53+
@Getter(AccessLevel.NONE)
54+
private final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
55+
credentialResponse;
56+
57+
/**
58+
* The {@link CredentialRecord} that was returned by {@link
59+
* CredentialRepositoryV2#lookup(ByteArray, ByteArray)} and whose public key was used to
60+
* successfully verify the assertion signature.
61+
*
62+
* <p>NOTE: The {@link CredentialRecord#getSignatureCount() signature count}, {@link
63+
* CredentialRecord#isBackupEligible() backup eligibility} and {@link
64+
* CredentialRecord#isBackedUp() backup state} properties in this object will reflect the state
65+
* <i>before</i> the assertion operation, not the new state. When updating your database state,
66+
* use the signature counter and backup state from {@link #getSignatureCount()}, {@link
67+
* #isBackupEligible()} and {@link #isBackedUp()} instead.
68+
*/
69+
private final C credential;
70+
71+
/**
72+
* <code>true</code> if and only if at least one of the following is true:
73+
*
74+
* <ul>
75+
* <li>The {@link AuthenticatorData#getSignatureCounter() signature counter value} in the
76+
* assertion was strictly greater than {@link CredentialRecord#getSignatureCount() the
77+
* stored one}.
78+
* <li>The {@link AuthenticatorData#getSignatureCounter() signature counter value} in the
79+
* assertion and {@link CredentialRecord#getSignatureCount() the stored one} were both zero.
80+
* </ul>
81+
*
82+
* @see <a
83+
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-authenticator-data">§6.1.
84+
* Authenticator Data</a>
85+
* @see AuthenticatorData#getSignatureCounter()
86+
* @see CredentialRecord#getSignatureCount()
87+
* @see RelyingParty.RelyingPartyBuilder#validateSignatureCounter(boolean)
88+
*/
89+
private final boolean signatureCounterValid;
90+
91+
@JsonCreator
92+
AssertionResultV2(
93+
@JsonProperty("success") boolean success,
94+
@NonNull @JsonProperty("credentialResponse")
95+
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
96+
credentialResponse,
97+
@NonNull @JsonProperty("credential") C credential,
98+
@JsonProperty("signatureCounterValid") boolean signatureCounterValid) {
99+
this.success = success;
100+
this.credentialResponse = credentialResponse;
101+
this.credential = credential;
102+
this.signatureCounterValid = signatureCounterValid;
103+
}
104+
105+
/**
106+
* Check whether the <a href="https://www.w3.org/TR/webauthn/#user-verification">user
107+
* verification</a> as performed during the authentication ceremony.
108+
*
109+
* <p>This flag is also available via <code>
110+
* {@link PublicKeyCredential}.{@link PublicKeyCredential#getResponse() getResponse()}.{@link AuthenticatorResponse#getParsedAuthenticatorData() getParsedAuthenticatorData()}.{@link AuthenticatorData#getFlags() getFlags()}.{@link AuthenticatorDataFlags#UV UV}
111+
* </code>.
112+
*
113+
* @return <code>true</code> if and only if the authenticator claims to have performed user
114+
* verification during the authentication ceremony.
115+
* @see <a href="https://www.w3.org/TR/webauthn/#user-verification">User Verification</a>
116+
* @see <a href="https://w3c.github.io/webauthn/#authdata-flags-uv">UV flag in §6.1. Authenticator
117+
* Data</a>
118+
*/
119+
@JsonIgnore
120+
public boolean isUserVerified() {
121+
return credentialResponse.getResponse().getParsedAuthenticatorData().getFlags().UV;
122+
}
123+
124+
/**
125+
* Check whether the asserted credential is <a
126+
* href="https://w3c.github.io/webauthn/#backup-eligible">backup eligible</a>, using the <a
127+
* href="https://w3c.github.io/webauthn/#authdata-flags-be">BE flag</a> in the authenticator data.
128+
*
129+
* <p>You SHOULD store this value in your representation of the corresponding {@link
130+
* CredentialRecord} if no value is stored yet. {@link CredentialRepository} implementations
131+
* SHOULD set this value when reconstructing that {@link CredentialRecord}.
132+
*
133+
* @return <code>true</code> if and only if the created credential is backup eligible. NOTE that
134+
* this is only a hint and not a guarantee, unless backed by a trusted authenticator
135+
* attestation.
136+
* @see <a href="https://w3c.github.io/webauthn/#backup-eligible">Backup Eligible in §4.
137+
* Terminology</a>
138+
* @see <a href="https://w3c.github.io/webauthn/#authdata-flags-be">BE flag in §6.1. Authenticator
139+
* Data</a>
140+
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
141+
* the standard matures.
142+
*/
143+
@Deprecated
144+
@JsonIgnore
145+
public boolean isBackupEligible() {
146+
return credentialResponse.getResponse().getParsedAuthenticatorData().getFlags().BE;
147+
}
148+
149+
/**
150+
* Get the current <a href="https://w3c.github.io/webauthn/#backup-state">backup state</a> of the
151+
* asserted credential, using the <a href="https://w3c.github.io/webauthn/#authdata-flags-bs">BS
152+
* flag</a> in the authenticator data.
153+
*
154+
* <p>You SHOULD update this value in your representation of a {@link CredentialRecord}. {@link
155+
* CredentialRepository} implementations SHOULD set this value when reconstructing that {@link
156+
* CredentialRecord}.
157+
*
158+
* @return <code>true</code> if and only if the created credential is believed to currently be
159+
* backed up. NOTE that this is only a hint and not a guarantee, unless backed by a trusted
160+
* authenticator attestation.
161+
* @see <a href="https://w3c.github.io/webauthn/#backup-state">Backup State in §4. Terminology</a>
162+
* @see <a href="https://w3c.github.io/webauthn/#authdata-flags-bs">BS flag in §6.1. Authenticator
163+
* Data</a>
164+
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
165+
* the standard matures.
166+
*/
167+
@Deprecated
168+
@JsonIgnore
169+
public boolean isBackedUp() {
170+
return credentialResponse.getResponse().getParsedAuthenticatorData().getFlags().BS;
171+
}
172+
173+
/**
174+
* The <a href="https://w3c.github.io/webauthn/#authenticator-attachment-modality">authenticator
175+
* attachment modality</a> in effect at the time the asserted credential was used.
176+
*
177+
* @see PublicKeyCredential#getAuthenticatorAttachment()
178+
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
179+
* the standard matures.
180+
*/
181+
@Deprecated
182+
@JsonIgnore
183+
public Optional<AuthenticatorAttachment> getAuthenticatorAttachment() {
184+
return credentialResponse.getAuthenticatorAttachment();
185+
}
186+
187+
/**
188+
* The new <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#signcount">signature
189+
* count</a> of the credential used for the assertion.
190+
*
191+
* <p>You should update this value in your database.
192+
*
193+
* @see AuthenticatorData#getSignatureCounter()
194+
*/
195+
@JsonIgnore
196+
public long getSignatureCount() {
197+
return credentialResponse.getResponse().getParsedAuthenticatorData().getSignatureCounter();
198+
}
199+
200+
/**
201+
* The <a
202+
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-extension-output">client
203+
* extension outputs</a>, if any.
204+
*
205+
* <p>This is present if and only if at least one extension output is present in the return value.
206+
*
207+
* @see <a
208+
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-client-extension-processing">§9.4.
209+
* Client Extension Processing</a>
210+
* @see ClientAssertionExtensionOutputs
211+
* @see #getAuthenticatorExtensionOutputs() ()
212+
*/
213+
@JsonIgnore
214+
public Optional<ClientAssertionExtensionOutputs> getClientExtensionOutputs() {
215+
return Optional.of(credentialResponse.getClientExtensionResults())
216+
.filter(ceo -> !ceo.getExtensionIds().isEmpty());
217+
}
218+
219+
/**
220+
* The <a
221+
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#authenticator-extension-output">authenticator
222+
* extension outputs</a>, if any.
223+
*
224+
* <p>This is present if and only if at least one extension output is present in the return value.
225+
*
226+
* @see <a
227+
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-authenticator-extension-processing">§9.5.
228+
* Authenticator Extension Processing</a>
229+
* @see AuthenticatorAssertionExtensionOutputs
230+
* @see #getClientExtensionOutputs()
231+
*/
232+
@JsonIgnore
233+
public Optional<AuthenticatorAssertionExtensionOutputs> getAuthenticatorExtensionOutputs() {
234+
return AuthenticatorAssertionExtensionOutputs.fromAuthenticatorData(
235+
credentialResponse.getResponse().getParsedAuthenticatorData());
236+
}
237+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.yubico.webauthn;
2+
3+
import com.yubico.webauthn.data.ByteArray;
4+
import java.util.Optional;
5+
import lombok.NonNull;
6+
7+
/**
8+
* @see <a href="https://w3c.github.io/webauthn/#credential-record">Credential Record</a>
9+
*/
10+
public interface CredentialRecord {
11+
12+
@NonNull
13+
ByteArray getCredentialId();
14+
15+
@NonNull
16+
ByteArray getUserHandle();
17+
18+
@NonNull
19+
ByteArray getPublicKeyCose();
20+
21+
long getSignatureCount();
22+
23+
// @NonNull
24+
// Set<AuthenticatorTransport> getTransports();
25+
26+
// boolean isUvInitialized();
27+
28+
Optional<Boolean> isBackupEligible();
29+
30+
Optional<Boolean> isBackedUp();
31+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import java.util.Set;
3131

3232
/**
33-
* An abstraction of the database lookups needed by this library.
33+
* An abstraction of the primary database lookups needed by this library.
3434
*
3535
* <p>This is used by {@link RelyingParty} to look up credentials, usernames and user handles from
3636
* usernames, user handles and credential IDs.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.yubico.webauthn;
2+
3+
import com.yubico.webauthn.data.ByteArray;
4+
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
5+
import java.util.Collections;
6+
import java.util.Optional;
7+
import java.util.Set;
8+
import lombok.AllArgsConstructor;
9+
10+
@AllArgsConstructor
11+
public class CredentialRepositoryV1ToV2Adapter
12+
implements CredentialRepositoryV2<RegisteredCredential>, UsernameRepository {
13+
14+
private final CredentialRepository inner;
15+
16+
@Override
17+
public Set<PublicKeyCredentialDescriptor> getCredentialIdsForUserHandle(ByteArray userHandle) {
18+
return inner
19+
.getUsernameForUserHandle(userHandle)
20+
.map(inner::getCredentialIdsForUsername)
21+
.orElseGet(Collections::emptySet);
22+
}
23+
24+
@Override
25+
public Optional<RegisteredCredential> lookup(ByteArray credentialId, ByteArray userHandle) {
26+
return inner.lookup(credentialId, userHandle);
27+
}
28+
29+
@Override
30+
public boolean credentialIdExists(ByteArray credentialId) {
31+
return !inner.lookupAll(credentialId).isEmpty();
32+
}
33+
34+
@Override
35+
public Optional<ByteArray> getUserHandleForUsername(String username) {
36+
return inner.getUserHandleForUsername(username);
37+
}
38+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) 2018, Yubico AB
2+
// All rights reserved.
3+
//
4+
// Redistribution and use in source and binary forms, with or without
5+
// modification, are permitted provided that the following conditions are met:
6+
//
7+
// 1. Redistributions of source code must retain the above copyright notice, this
8+
// list of conditions and the following disclaimer.
9+
//
10+
// 2. Redistributions in binary form must reproduce the above copyright notice,
11+
// this list of conditions and the following disclaimer in the documentation
12+
// and/or other materials provided with the distribution.
13+
//
14+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17+
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20+
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22+
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24+
25+
package com.yubico.webauthn;
26+
27+
import com.yubico.webauthn.data.ByteArray;
28+
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
29+
import java.util.Optional;
30+
import java.util.Set;
31+
32+
/**
33+
* An abstraction of database lookups needed by this library.
34+
*
35+
* <p>This is used by {@link RelyingParty} to look up credentials and credential IDs.
36+
*
37+
* <p>Unlike {@link CredentialRepository}, this interface does not require support for usernames.
38+
*/
39+
public interface CredentialRepositoryV2<C extends CredentialRecord> {
40+
41+
/**
42+
* Get the credential IDs of all credentials registered to the user with the given user handle.
43+
*
44+
* <p>After a successful registration ceremony, the {@link RegistrationResult#getKeyId()} method
45+
* returns a value suitable for inclusion in this set.
46+
*
47+
* @return a {@link Set} containing one {@link PublicKeyCredentialDescriptor} for each credential
48+
* registered to the given user. The set MUST NOT be null, but MAY be empty if the user does
49+
* not exist or has no credentials.
50+
*/
51+
Set<PublicKeyCredentialDescriptor> getCredentialIdsForUserHandle(ByteArray userHandle);
52+
53+
/**
54+
* Look up the public key, backup flags and current signature count for the given credential
55+
* registered to the given user.
56+
*
57+
* <p>The returned {@link RegisteredCredential} is not expected to be long-lived. It may be read
58+
* directly from a database or assembled from other components.
59+
*
60+
* @return a {@link RegisteredCredential} describing the current state of the registered
61+
* credential with credential ID <code>credentialId</code>, if any. If the credential does not
62+
* exist or is registered to a different user handle than <code>userHandle</code>, return
63+
* {@link Optional#empty()}.
64+
*/
65+
Optional<C> lookup(ByteArray credentialId, ByteArray userHandle);
66+
67+
/**
68+
* Check whether any credential exists with the given credential ID, regardless of what user it is
69+
* registered to.
70+
*
71+
* <p>This is used to refuse registration of duplicate credential IDs.
72+
*
73+
* @return <code>true</code> if and only if the credential database contains at least one
74+
* credential with the given credential ID.
75+
*/
76+
boolean credentialIdExists(ByteArray credentialId);
77+
}

0 commit comments

Comments
 (0)