[java-idp-plugin-webauthn] branch main updated: Add more usernameless tests
Phil Smart
philip.smart at jisc.ac.uk
Mon Aug 5 15:59:35 UTC 2024
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-webauthn.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-webauthn.git;a=commit;h=d47f8a1d1570a082667df329135da9d439cedc63
The following commit(s) were added to refs/heads/main by this push:
new d47f8a1 Add more usernameless tests
d47f8a1 is described below
commit d47f8a1d1570a082667df329135da9d439cedc63
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Aug 5 16:59:32 2024 +0100
Add more usernameless tests
---
.../LookupRegisteredCredentialsFromUserHandle.java | 4 +-
.../webauthn/flow/AbstractWebAuthnFlowTest.java | 30 +++++
.../authn/webauthn/flow/TestUsernameslessFlow.java | 127 +++++++++++++++++++++
3 files changed, 159 insertions(+), 2 deletions(-)
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java
index 41e9311..7f1242f 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java
@@ -48,8 +48,8 @@ import net.shibboleth.shared.primitive.LoggerFactory;
* An action that lookups existing registered credentials from the userHandle supplied in the authenticators assertion
* response. If no credentials exist and the trigger condition is set, an error event will be produced.
*
- * <p>Does not actually set the found credentials back onto the context. This is a sanity check credentials exist, and
- * allows flow branching if none are found.</p>
+ * <p>Does not actually set the found credentials back onto the context. This is a sanity check that credentials exist,
+ * and allows flow branching if none are found.</p>
*
* <p>Also ensures the username found from the userHandle matches that in the authentication context if supplied e.g.
* for a passwordless authentication.</p>
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
index 5da1885..aae9c29 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
@@ -17,6 +17,7 @@ package net.shibboleth.idp.plugin.authn.webauthn.flow;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@@ -25,6 +26,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
+import java.util.Random;
import java.util.TreeSet;
import javax.annotation.Nonnull;
@@ -229,6 +231,19 @@ public class AbstractWebAuthnFlowTest extends AbstractFlowTest {
return reg;
}
+ /**
+ * Generate a 'number' of random bytes
+ *
+ * @param number the number of random bytes
+ *
+ * @return the random bytes
+ */
+ protected byte[] generateRandomBytes(final int number) {
+ final Random random = new Random();
+ final byte[] byteArray = new byte[number];
+ random.nextBytes(byteArray);
+ return byteArray;
+ }
/**
* Assert the PublicKeyCredentialCreationOptions has been created, and is correct.
@@ -296,6 +311,21 @@ public class AbstractWebAuthnFlowTest extends AbstractFlowTest {
assertEquals(subject.getPrincipals(WebAuthnUserIdPrinicpal.class).iterator().next().getName(),userId);
}
+ /**
+ * Assert conditions required to test the WebAuthn flow has produced an invalid authentication result.
+ *
+ * @param prc the profile request context
+ *
+ * @throws EncodingException on error
+ */
+ @SuppressWarnings("null")
+ protected void assertAuthenticationFailureConditions(final ProfileRequestContext prc) throws EncodingException {
+ assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+ final var ac = prc.getSubcontext(AuthenticationContext.class);
+ assertNull(ac.getAuthenticationResult());
+
+ }
+
/**
* Get the {@link WebAuthnAuthenticationContext}.
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlow.java
index 1c2b136..81fa5ec 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlow.java
@@ -105,5 +105,132 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
assertAuthenticationSuccessConditions(prc, true);
}
+
+ @SuppressWarnings("null")
+ @Test
+ public void testUsernamelessFlow_NoAssertionResponse() throws Exception {
+
+ //Register a credential for use.
+ final CredentialRegistration registration =
+ createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+ credentialRepo.addRegistrationByUsername(USERNAME, registration);
+
+ final var prc = buildProfileRequestContext(false, false, null);
+
+ final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext,
+ addToConversationScopeMap(Map.of("opensamlProfileRequestContext", prc)));
+
+ assertFlowExecutionActive(result.getSecond());
+ assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+ assertPublicKeyCredentialRequestOptions(prc, false, true);
+
+ // Do assertion validation half of flow
+
+ // Get the challenge that was set into the PublicKeyCredentialRequestOptions.
+ // otherwise we will end up signing a different one, which will provide its own test.
+ final WebAuthnAuthenticationContext authnContext = getWebAuthnAuthenticationContext(prc);
+ final ByteArray challenge = authnContext.getPublicKeyCredentialRequestOptions().getChallenge();
+
+ final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+ assertionResponse = createAssertionReponseFromCredentialID(
+ registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+
+ final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
+
+ // Re-set external context to holder
+ ExternalContextHolder.setExternalContext(externalContext);
+ // Do not add assertion response
+ externalContext.setEventId("proceed");
+ result.getSecond().setCurrentState("DisplayWebAuthnView");
+ result.getSecond().resume(externalContext);
+
+ // assert end conditions, no existing result or principal. So this should produce a Username principal
+ assertEquals(result.getSecond().getOutcome().getId(), "NoCredentials");
+ assertAuthenticationFailureConditions(prc);
+
+ }
+
+ @SuppressWarnings("null")
+ @Test
+ public void testUsernamelessFlow_DifferentChallenge() throws Exception {
+
+ //Register a credential for use.
+ final CredentialRegistration registration =
+ createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+ credentialRepo.addRegistrationByUsername(USERNAME, registration);
+
+ final var prc = buildProfileRequestContext(false, false, null);
+
+ final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext,
+ addToConversationScopeMap(Map.of("opensamlProfileRequestContext", prc)));
+
+ assertFlowExecutionActive(result.getSecond());
+ assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+ assertPublicKeyCredentialRequestOptions(prc, false, true);
+
+ // Do assertion validation half of flow
+
+ final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+ // Generate a challenge which is not the same as the one generated by the flow
+ assertionResponse = createAssertionReponseFromCredentialID(
+ registration.getCredential().getCredentialId().getBytes(), generateRandomBytes(32));
+
+ final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
+
+ // Re-set external context to holder
+ ExternalContextHolder.setExternalContext(externalContext);
+ setHttpFormRequest("POST", Map.of(ExtractPublicKeyCredentialAssertionFromFormRequest.DEFAULT_PARAMETER_NAME,
+ assertionResponseJson));
+ externalContext.setEventId("proceed");
+ result.getSecond().setCurrentState("DisplayWebAuthnView");
+ result.getSecond().resume(externalContext);
+
+ assertEquals(result.getSecond().getOutcome().getId(), "InvalidCredentials");
+ assertAuthenticationFailureConditions(prc);
+
+ }
+
+ @SuppressWarnings("null")
+ @Test
+ public void testUsernamelessFlow_NoRegisteredCredential() throws Exception {
+
+ //Create a credential to use, but do not register it
+ final CredentialRegistration registration =
+ createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+
+ final var prc = buildProfileRequestContext(false, false, null);
+
+ final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext,
+ addToConversationScopeMap(Map.of("opensamlProfileRequestContext", prc)));
+
+ assertFlowExecutionActive(result.getSecond());
+ assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+ assertPublicKeyCredentialRequestOptions(prc, false, true);
+
+ // Do assertion validation half of flow
+
+ // Get the challenge that was set into the PublicKeyCredentialRequestOptions.
+ // otherwise we will end up signing a different one, which will provide its own test.
+ final WebAuthnAuthenticationContext authnContext = getWebAuthnAuthenticationContext(prc);
+ final ByteArray challenge = authnContext.getPublicKeyCredentialRequestOptions().getChallenge();
+
+ final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+ assertionResponse = createAssertionReponseFromCredentialID(
+ registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+
+ final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
+
+ // Re-set external context to holder
+ ExternalContextHolder.setExternalContext(externalContext);
+ setHttpFormRequest("POST", Map.of(ExtractPublicKeyCredentialAssertionFromFormRequest.DEFAULT_PARAMETER_NAME,
+ assertionResponseJson));
+ externalContext.setEventId("proceed");
+ result.getSecond().setCurrentState("DisplayWebAuthnView");
+ result.getSecond().resume(externalContext);
+
+ assertEquals(result.getSecond().getOutcome().getId(), "InvalidCredentials");
+ assertAuthenticationFailureConditions(prc);
+
+ }
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list