[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