2525package com .yubico .webauthn ;
2626
2727import com .fasterxml .jackson .annotation .JsonCreator ;
28+ import com .fasterxml .jackson .annotation .JsonIgnore ;
2829import com .fasterxml .jackson .annotation .JsonProperty ;
29- import com .yubico .internal .util .ExceptionUtil ;
3030import com .yubico .webauthn .data .AuthenticatorAssertionExtensionOutputs ;
31+ import com .yubico .webauthn .data .AuthenticatorAssertionResponse ;
3132import com .yubico .webauthn .data .AuthenticatorData ;
3233import com .yubico .webauthn .data .ByteArray ;
3334import com .yubico .webauthn .data .ClientAssertionExtensionOutputs ;
35+ import com .yubico .webauthn .data .PublicKeyCredential ;
3436import com .yubico .webauthn .data .PublicKeyCredentialRequestOptions ;
3537import com .yubico .webauthn .data .UserIdentity ;
3638import java .util .Optional ;
37- import lombok .Builder ;
39+ import lombok .AccessLevel ;
40+ import lombok .Getter ;
3841import lombok .NonNull ;
3942import lombok .Value ;
4043
4144/** The result of a call to {@link RelyingParty#finishAssertion(FinishAssertionOptions)}. */
4245@ Value
43- @ Builder (toBuilder = true )
4446public class AssertionResult {
4547
4648 /** <code>true</code> if the assertion was verified successfully. */
4749 private final boolean success ;
4850
51+ @ JsonProperty
52+ @ Getter (AccessLevel .NONE )
53+ private final PublicKeyCredential <AuthenticatorAssertionResponse , ClientAssertionExtensionOutputs >
54+ credentialResponse ;
55+
4956 /**
5057 * The {@link RegisteredCredential} that was returned by {@link
5158 * CredentialRepository#lookup(ByteArray, ByteArray)} and whose public key was used to
5259 * successfully verify the assertion signature.
5360 *
54- * <p>NOTE: The {@link RegisteredCredential#getSignatureCount() signature count} in this object
55- * will reflect the signature counter state <i>before</i> the assertion operation, not the new
56- * counter value. When updating your database state, use the signature counter from {@link
57- * #getSignatureCount()} instead.
61+ * <p>NOTE: The {@link RegisteredCredential#getSignatureCount() signature count}, {@link
62+ * RegisteredCredential#isBackupEligible() backup eligibility} and {@link
63+ * RegisteredCredential#isBackedUp() backup state} properties in this object will reflect the
64+ * state <i>before</i> the assertion operation, not the new state. When updating your database
65+ * state, use the signature counter and backup state from {@link #getSignatureCount()}, {@link
66+ * #isBackupEligible()} and {@link #isBackedUp()} instead.
5867 */
5968 private final RegisteredCredential credential ;
6069
@@ -65,16 +74,6 @@ public class AssertionResult {
6574 */
6675 @ NonNull private final String username ;
6776
68- /**
69- * The new <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#signcount">signature
70- * count</a> of the credential used for the assertion.
71- *
72- * <p>You should update this value in your database.
73- *
74- * @see AuthenticatorData#getSignatureCounter()
75- */
76- private final long signatureCount ;
77-
7877 /**
7978 * <code>true</code> if and only if at least one of the following is true:
8079 *
@@ -96,65 +95,20 @@ public class AssertionResult {
9695 */
9796 private final boolean signatureCounterValid ;
9897
99- private final ClientAssertionExtensionOutputs clientExtensionOutputs ;
100-
101- private final AuthenticatorAssertionExtensionOutputs authenticatorExtensionOutputs ;
102-
103- private AssertionResult (
104- boolean success ,
105- @ NonNull @ JsonProperty ("credential" ) RegisteredCredential credential ,
106- @ NonNull String username ,
107- long signatureCount ,
108- boolean signatureCounterValid ,
109- ClientAssertionExtensionOutputs clientExtensionOutputs ,
110- AuthenticatorAssertionExtensionOutputs authenticatorExtensionOutputs ) {
111- this (
112- success ,
113- credential ,
114- username ,
115- null ,
116- null ,
117- signatureCount ,
118- signatureCounterValid ,
119- clientExtensionOutputs ,
120- authenticatorExtensionOutputs );
121- }
122-
12398 @ JsonCreator
124- private AssertionResult (
99+ AssertionResult (
125100 @ JsonProperty ("success" ) boolean success ,
101+ @ NonNull @ JsonProperty ("credentialResponse" )
102+ PublicKeyCredential <AuthenticatorAssertionResponse , ClientAssertionExtensionOutputs >
103+ credentialResponse ,
126104 @ NonNull @ JsonProperty ("credential" ) RegisteredCredential credential ,
127105 @ NonNull @ JsonProperty ("username" ) String username ,
128- @ JsonProperty ("credentialId" ) ByteArray credentialId , // TODO: Delete in next major release
129- @ JsonProperty ("userHandle" ) ByteArray userHandle , // TODO: Delete in next major release
130- @ JsonProperty ("signatureCount" ) long signatureCount ,
131- @ JsonProperty ("signatureCounterValid" ) boolean signatureCounterValid ,
132- @ JsonProperty ("clientExtensionOutputs" )
133- ClientAssertionExtensionOutputs clientExtensionOutputs ,
134- @ JsonProperty ("authenticatorExtensionOutputs" )
135- AuthenticatorAssertionExtensionOutputs authenticatorExtensionOutputs ) {
106+ @ JsonProperty ("signatureCounterValid" ) boolean signatureCounterValid ) {
136107 this .success = success ;
108+ this .credentialResponse = credentialResponse ;
137109 this .credential = credential ;
138110 this .username = username ;
139-
140- if (credentialId != null ) {
141- ExceptionUtil .assure (
142- credential .getCredentialId ().equals (credentialId ),
143- "Legacy credentialId is present and does not equal credential.credentialId" );
144- }
145- if (userHandle != null ) {
146- ExceptionUtil .assure (
147- credential .getUserHandle ().equals (userHandle ),
148- "Legacy userHandle is present and does not equal credential.userHandle" );
149- }
150-
151- this .signatureCount = signatureCount ;
152111 this .signatureCounterValid = signatureCounterValid ;
153- this .clientExtensionOutputs =
154- clientExtensionOutputs == null || clientExtensionOutputs .getExtensionIds ().isEmpty ()
155- ? null
156- : clientExtensionOutputs ;
157- this .authenticatorExtensionOutputs = authenticatorExtensionOutputs ;
158112 }
159113
160114 /**
@@ -168,6 +122,7 @@ private AssertionResult(
168122 * getCredentialId()} instead.
169123 */
170124 @ Deprecated
125+ @ JsonIgnore
171126 public ByteArray getCredentialId () {
172127 return credential .getCredentialId ();
173128 }
@@ -183,10 +138,76 @@ public ByteArray getCredentialId() {
183138 * getUserHandle()} instead.
184139 */
185140 @ Deprecated
141+ @ JsonIgnore
186142 public ByteArray getUserHandle () {
187143 return credential .getUserHandle ();
188144 }
189145
146+ /**
147+ * Check whether the asserted credential is <a
148+ * href="https://w3c.github.io/webauthn/#backup-eligible">backup eligible</a>, using the <a
149+ * href="https://w3c.github.io/webauthn/#authdata-flags-be">BE flag</a> in the authenticator data.
150+ *
151+ * <p>You SHOULD store this value in your representation of the corresponding {@link
152+ * RegisteredCredential} if no value is stored yet. {@link CredentialRepository} implementations
153+ * SHOULD set this value as the {@link
154+ * RegisteredCredential.RegisteredCredentialBuilder#backupEligible(Boolean)
155+ * backupEligible(Boolean)} value when reconstructing that {@link RegisteredCredential}.
156+ *
157+ * @return <code>true</code> if and only if the created credential is backup eligible. NOTE that
158+ * this is only a hint and not a guarantee, unless backed by a trusted authenticator
159+ * attestation.
160+ * @see <a href="https://w3c.github.io/webauthn/#backup-eligible">Backup Eligible in §4.
161+ * Terminology</a>
162+ * @see <a href="https://w3c.github.io/webauthn/#authdata-flags-be">BE 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 isBackupEligible () {
170+ return credentialResponse .getResponse ().getParsedAuthenticatorData ().getFlags ().BE ;
171+ }
172+
173+ /**
174+ * Get the current <a href="https://w3c.github.io/webauthn/#backup-state">backup state</a> of the
175+ * asserted credential, using the <a href="https://w3c.github.io/webauthn/#authdata-flags-bs">BS
176+ * flag</a> in the authenticator data.
177+ *
178+ * <p>You SHOULD update this value in your representation of a {@link RegisteredCredential}.
179+ * {@link CredentialRepository} implementations SHOULD set this value as the {@link
180+ * RegisteredCredential.RegisteredCredentialBuilder#backupState(Boolean) backupState(Boolean)}
181+ * value when reconstructing that {@link RegisteredCredential}.
182+ *
183+ * @return <code>true</code> if and only if the created credential is believed to currently be
184+ * backed up. NOTE that this is only a hint and not a guarantee, unless backed by a trusted
185+ * authenticator attestation.
186+ * @see <a href="https://w3c.github.io/webauthn/#backup-state">Backup State in §4. Terminology</a>
187+ * @see <a href="https://w3c.github.io/webauthn/#authdata-flags-bs">BS flag in §6.1. Authenticator
188+ * Data</a>
189+ * @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
190+ * the standard matures.
191+ */
192+ @ Deprecated
193+ @ JsonIgnore
194+ public boolean isBackedUp () {
195+ return credentialResponse .getResponse ().getParsedAuthenticatorData ().getFlags ().BS ;
196+ }
197+
198+ /**
199+ * The new <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#signcount">signature
200+ * count</a> of the credential used for the assertion.
201+ *
202+ * <p>You should update this value in your database.
203+ *
204+ * @see AuthenticatorData#getSignatureCounter()
205+ */
206+ @ JsonIgnore
207+ public long getSignatureCount () {
208+ return credentialResponse .getResponse ().getParsedAuthenticatorData ().getSignatureCounter ();
209+ }
210+
190211 /**
191212 * The <a
192213 * href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-extension-output">client
@@ -200,8 +221,10 @@ public ByteArray getUserHandle() {
200221 * @see ClientAssertionExtensionOutputs
201222 * @see #getAuthenticatorExtensionOutputs() ()
202223 */
224+ @ JsonIgnore
203225 public Optional <ClientAssertionExtensionOutputs > getClientExtensionOutputs () {
204- return Optional .ofNullable (clientExtensionOutputs );
226+ return Optional .of (credentialResponse .getClientExtensionResults ())
227+ .filter (ceo -> !ceo .getExtensionIds ().isEmpty ());
205228 }
206229
207230 /**
@@ -217,65 +240,9 @@ public Optional<ClientAssertionExtensionOutputs> getClientExtensionOutputs() {
217240 * @see AuthenticatorAssertionExtensionOutputs
218241 * @see #getClientExtensionOutputs()
219242 */
243+ @ JsonIgnore
220244 public Optional <AuthenticatorAssertionExtensionOutputs > getAuthenticatorExtensionOutputs () {
221- return Optional .ofNullable (authenticatorExtensionOutputs );
222- }
223-
224- static AssertionResultBuilder .MandatoryStages builder () {
225- return new AssertionResultBuilder .MandatoryStages ();
226- }
227-
228- static class AssertionResultBuilder {
229- public static class MandatoryStages {
230- private final AssertionResultBuilder builder = new AssertionResultBuilder ();
231-
232- public Step2 success (boolean success ) {
233- builder .success (success );
234- return new Step2 ();
235- }
236-
237- public class Step2 {
238- public Step3 credential (RegisteredCredential credential ) {
239- builder .credential (credential );
240- return new Step3 ();
241- }
242- }
243-
244- public class Step3 {
245- public Step4 username (String username ) {
246- builder .username (username );
247- return new Step4 ();
248- }
249- }
250-
251- public class Step4 {
252- public Step5 signatureCount (long signatureCount ) {
253- builder .signatureCount (signatureCount );
254- return new Step5 ();
255- }
256- }
257-
258- public class Step5 {
259- public Step6 signatureCounterValid (boolean signatureCounterValid ) {
260- builder .signatureCounterValid (signatureCounterValid );
261- return new Step6 ();
262- }
263- }
264-
265- public class Step6 {
266- public Step7 clientExtensionOutputs (
267- ClientAssertionExtensionOutputs clientExtensionOutputs ) {
268- builder .clientExtensionOutputs (clientExtensionOutputs );
269- return new Step7 ();
270- }
271- }
272-
273- public class Step7 {
274- public AssertionResultBuilder assertionExtensionOutputs (
275- AuthenticatorAssertionExtensionOutputs authenticatorExtensionOutputs ) {
276- return builder .authenticatorExtensionOutputs (authenticatorExtensionOutputs );
277- }
278- }
279- }
245+ return AuthenticatorAssertionExtensionOutputs .fromAuthenticatorData (
246+ credentialResponse .getResponse ().getParsedAuthenticatorData ());
280247 }
281248}
0 commit comments