Skip to content

Commit e91d042

Browse files
committed
Eliminate redundant calls to CredentialRepository.lookup
1 parent ddb1af3 commit e91d042

File tree

3 files changed

+152
-52
lines changed

3 files changed

+152
-52
lines changed

NEWS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
== Version 1.12.1 (unreleased) ==
2+
3+
Fixes:
4+
5+
* `RelyingParty.finishAssertion()` no longer makes multiple (redundant) calls to
6+
`CredentialRepository.lookup()`.
7+
8+
19
== Version 1.12.0 ==
210

311
New features:

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

Lines changed: 52 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,27 @@ default AssertionResult run() throws InvalidSignatureCountException {
117117

118118
@Value
119119
class Step0 implements Step<Step1> {
120+
121+
private final Optional<ByteArray> userHandle =
122+
response
123+
.getResponse()
124+
.getUserHandle()
125+
.map(Optional::of)
126+
.orElseGet(
127+
() -> credentialRepository.getUserHandleForUsername(request.getUsername().get()));
128+
129+
private final Optional<String> username =
130+
request
131+
.getUsername()
132+
.map(Optional::of)
133+
.orElseGet(
134+
() ->
135+
credentialRepository.getUsernameForUserHandle(
136+
response.getResponse().getUserHandle().get()));
137+
120138
@Override
121139
public Step1 nextStep() {
122-
return new Step1(username().get(), userHandle().get(), allWarnings());
140+
return new Step1(username.get(), userHandle.get(), allWarnings());
123141
}
124142

125143
@Override
@@ -128,12 +146,12 @@ public void validate() {
128146
request.getUsername().isPresent() || response.getResponse().getUserHandle().isPresent(),
129147
"At least one of username and user handle must be given; none was.");
130148
assure(
131-
userHandle().isPresent(),
149+
userHandle.isPresent(),
132150
"No user found for username: %s, userHandle: %s",
133151
request.getUsername(),
134152
response.getResponse().getUserHandle());
135153
assure(
136-
username().isPresent(),
154+
username.isPresent(),
137155
"No user found for username: %s, userHandle: %s",
138156
request.getUsername(),
139157
response.getResponse().getUserHandle());
@@ -143,25 +161,6 @@ public void validate() {
143161
public List<String> getPrevWarnings() {
144162
return Collections.emptyList();
145163
}
146-
147-
private Optional<ByteArray> userHandle() {
148-
return response
149-
.getResponse()
150-
.getUserHandle()
151-
.map(Optional::of)
152-
.orElseGet(
153-
() -> credentialRepository.getUserHandleForUsername(request.getUsername().get()));
154-
}
155-
156-
private Optional<String> username() {
157-
return request
158-
.getUsername()
159-
.map(Optional::of)
160-
.orElseGet(
161-
() ->
162-
credentialRepository.getUsernameForUserHandle(
163-
response.getResponse().getUserHandle().get()));
164-
}
165164
}
166165

167166
@Value
@@ -196,16 +195,22 @@ class Step2 implements Step<Step3> {
196195
private final ByteArray userHandle;
197196
private final List<String> prevWarnings;
198197

198+
private final Optional<RegisteredCredential> registration;
199+
200+
public Step2(String username, ByteArray userHandle, List<String> prevWarnings) {
201+
this.username = username;
202+
this.userHandle = userHandle;
203+
this.prevWarnings = prevWarnings;
204+
this.registration = credentialRepository.lookup(response.getId(), userHandle);
205+
}
206+
199207
@Override
200208
public Step3 nextStep() {
201-
return new Step3(username, userHandle, allWarnings());
209+
return new Step3(username, userHandle, registration, allWarnings());
202210
}
203211

204212
@Override
205213
public void validate() {
206-
Optional<RegisteredCredential> registration =
207-
credentialRepository.lookup(response.getId(), userHandle);
208-
209214
assure(registration.isPresent(), "Unknown credential: %s", response.getId());
210215

211216
assure(
@@ -220,29 +225,22 @@ public void validate() {
220225
class Step3 implements Step<Step4> {
221226
private final String username;
222227
private final ByteArray userHandle;
228+
private final Optional<RegisteredCredential> credential;
223229
private final List<String> prevWarnings;
224230

225231
@Override
226232
public Step4 nextStep() {
227-
return new Step4(username, userHandle, credential(), allWarnings());
233+
return new Step4(username, userHandle, credential.get(), allWarnings());
228234
}
229235

230236
@Override
231237
public void validate() {
232238
assure(
233-
maybeCredential().isPresent(),
239+
credential.isPresent(),
234240
"Unknown credential. Credential ID: %s, user handle: %s",
235241
response.getId(),
236242
userHandle);
237243
}
238-
239-
private Optional<RegisteredCredential> maybeCredential() {
240-
return credentialRepository.lookup(response.getId(), userHandle);
241-
}
242-
243-
public RegisteredCredential credential() {
244-
return maybeCredential().get();
245-
}
246244
}
247245

248246
@Value
@@ -581,7 +579,7 @@ public void validate() {
581579

582580
@Override
583581
public Step17 nextStep() {
584-
return new Step17(username, userHandle, allWarnings());
582+
return new Step17(username, userHandle, credential, allWarnings());
585583
}
586584

587585
public ByteArray signedBytes() {
@@ -593,19 +591,33 @@ public ByteArray signedBytes() {
593591
class Step17 implements Step<Finished> {
594592
private final String username;
595593
private final ByteArray userHandle;
594+
private final RegisteredCredential credential;
596595
private final List<String> prevWarnings;
596+
private final long storedSignatureCountBefore;
597+
598+
public Step17(
599+
String username,
600+
ByteArray userHandle,
601+
RegisteredCredential credential,
602+
List<String> prevWarnings) {
603+
this.username = username;
604+
this.userHandle = userHandle;
605+
this.credential = credential;
606+
this.prevWarnings = prevWarnings;
607+
this.storedSignatureCountBefore = credential.getSignatureCount();
608+
}
597609

598610
@Override
599611
public void validate() throws InvalidSignatureCountException {
600612
if (validateSignatureCounter && !signatureCounterValid()) {
601613
throw new InvalidSignatureCountException(
602-
response.getId(), storedSignatureCountBefore() + 1, assertionSignatureCount());
614+
response.getId(), storedSignatureCountBefore + 1, assertionSignatureCount());
603615
}
604616
}
605617

606618
private boolean signatureCounterValid() {
607-
return (assertionSignatureCount() == 0 && storedSignatureCountBefore() == 0)
608-
|| assertionSignatureCount() > storedSignatureCountBefore();
619+
return (assertionSignatureCount() == 0 && storedSignatureCountBefore == 0)
620+
|| assertionSignatureCount() > storedSignatureCountBefore;
609621
}
610622

611623
@Override
@@ -614,13 +626,6 @@ public Finished nextStep() {
614626
username, userHandle, assertionSignatureCount(), signatureCounterValid(), allWarnings());
615627
}
616628

617-
private long storedSignatureCountBefore() {
618-
return credentialRepository
619-
.lookup(response.getId(), userHandle)
620-
.map(RegisteredCredential::getSignatureCount)
621-
.orElse(0L);
622-
}
623-
624629
private long assertionSignatureCount() {
625630
return response.getResponse().getParsedAuthenticatorData().getSignatureCounter();
626631
}

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

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -329,9 +329,98 @@ class RelyingPartyAssertionSpec
329329

330330
}
331331

332-
describe("§7.2. Verifying an authentication assertion") {
332+
describe("RelyingParty.finishAssertion") {
333333

334-
describe("When verifying a given PublicKeyCredential structure (credential) and an AuthenticationExtensionsClientOutputs structure clientExtensionResults, as part of an authentication ceremony, the Relying Party MUST proceed as follows:") {
334+
it("does not make redundant calls to CredentialRepository.lookup().") {
335+
val registrationTestData =
336+
RegistrationTestData.Packed.BasicAttestationEdDsa
337+
val testData = registrationTestData.assertion.get
338+
339+
val credRepo = {
340+
val user = registrationTestData.userId
341+
val credential = RegisteredCredential
342+
.builder()
343+
.credentialId(registrationTestData.response.getId)
344+
.userHandle(registrationTestData.userId.getId)
345+
.publicKeyCose(
346+
registrationTestData.response.getResponse.getParsedAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey
347+
)
348+
.signatureCount(0)
349+
.build()
350+
351+
new CredentialRepository {
352+
var lookupCount = 0
353+
354+
override def getCredentialIdsForUsername(
355+
username: String
356+
): java.util.Set[PublicKeyCredentialDescriptor] =
357+
if (username == user.getName)
358+
Set(
359+
PublicKeyCredentialDescriptor
360+
.builder()
361+
.id(credential.getCredentialId)
362+
.build()
363+
).asJava
364+
else Set.empty.asJava
365+
366+
override def getUserHandleForUsername(
367+
username: String
368+
): Optional[ByteArray] =
369+
if (username == user.getName)
370+
Some(user.getId).asJava
371+
else None.asJava
372+
373+
override def getUsernameForUserHandle(
374+
userHandle: ByteArray
375+
): Optional[String] =
376+
if (userHandle == user.getId)
377+
Some(user.getName).asJava
378+
else None.asJava
379+
380+
override def lookup(
381+
credentialId: ByteArray,
382+
userHandle: ByteArray,
383+
): Optional[RegisteredCredential] = {
384+
lookupCount += 1
385+
if (
386+
credentialId == credential.getCredentialId && userHandle == user.getId
387+
)
388+
Some(credential).asJava
389+
else None.asJava
390+
}
391+
392+
override def lookupAll(
393+
credentialId: ByteArray
394+
): java.util.Set[RegisteredCredential] =
395+
if (credentialId == credential.getCredentialId)
396+
Set(credential).asJava
397+
else Set.empty.asJava
398+
}
399+
}
400+
val rp = RelyingParty
401+
.builder()
402+
.identity(
403+
RelyingPartyIdentity.builder().id("localhost").name("Test RP").build()
404+
)
405+
.credentialRepository(credRepo)
406+
.build()
407+
408+
val result = rp.finishAssertion(
409+
FinishAssertionOptions
410+
.builder()
411+
.request(testData.request)
412+
.response(testData.response)
413+
.build()
414+
)
415+
416+
result.isSuccess should be(true)
417+
result.getUserHandle should equal(registrationTestData.userId.getId)
418+
result.getCredentialId should equal(registrationTestData.response.getId)
419+
result.getCredentialId should equal(testData.response.getId)
420+
credRepo.lookupCount should equal(1)
421+
}
422+
423+
describe("§7.2. Verifying an authentication assertion: When verifying a given PublicKeyCredential structure (credential) and an AuthenticationExtensionsClientOutputs structure clientExtensionResults, as part of an authentication ceremony, the Relying Party MUST proceed as follows:") {
335424

336425
describe("1. If the allowCredentials option was given when this authentication ceremony was initiated, verify that credential.id identifies one of the public key credentials that were listed in allowCredentials.") {
337426
it("Fails if returned credential ID is a requested one.") {
@@ -497,6 +586,7 @@ class RelyingPartyAssertionSpec
497586
val step: steps.Step3 = new steps.Step3(
498587
Defaults.username,
499588
Defaults.userHandle,
589+
None.asJava,
500590
Nil.asJava,
501591
)
502592

@@ -523,9 +613,6 @@ class RelyingPartyAssertionSpec
523613
val step: FinishAssertionSteps#Step3 = steps.begin.next.next.next
524614

525615
step.validations shouldBe a[Success[_]]
526-
step.credential.getPublicKeyCose should equal(
527-
getPublicKeyBytes(Defaults.credentialKey)
528-
)
529616
step.tryNext shouldBe a[Success[_]]
530617
}
531618
}

0 commit comments

Comments
 (0)