[java-idp-plugin-webauthn] branch main updated: Add more unit tests

Phil Smart philip.smart at jisc.ac.uk
Fri Mar 22 12:45:04 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=55d1358d1506180b284259b7d51ce0b850d28146

The following commit(s) were added to refs/heads/main by this push:
     new 55d1358  Add more unit tests
55d1358 is described below

commit 55d1358d1506180b284259b7d51ce0b850d28146
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Mar 22 12:45:01 2024 +0000

    Add more unit tests
---
 .../webauthn/impl/LookupRegisteredCredentials.java |   3 +-
 .../admin/impl/DeletePublicKeyCredentialTest.java  | 180 +++++++++++++++++++++
 .../webauthn/impl/GenerateServerChallengeTest.java |  89 ++++++++++
 .../impl/LookupRegisteredCredentialsTest.java      | 138 ++++++++++++++++
 .../authn/webauthn/impl/MockAuthenticator.java     |  22 +++
 5 files changed, 431 insertions(+), 1 deletion(-)

diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
index 7506ce2..7698947 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
@@ -19,6 +19,7 @@ import java.util.Collection;
 import javax.annotation.Nonnull;
 
 import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
@@ -47,7 +48,7 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
         final String username = context.getUsername();
         if (username == null) {
             log.error("{} Unable to find username in registration context", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return;
         } 
         
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredentialTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredentialTest.java
new file mode 100644
index 0000000..495e6e8
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredentialTest.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeSet;
+
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.yubico.webauthn.RegisteredCredential;
+import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
+import com.yubico.webauthn.data.AuthenticatorTransport;
+import com.yubico.webauthn.data.ByteArray;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
+import com.yubico.webauthn.data.UserIdentity;
+
+import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Tests for {@link DeletePublicKeyCredential}
+ */
+public class DeletePublicKeyCredentialTest extends AbstractWebAuthnTest {
+    
+    private DeletePublicKeyCredential action;
+    
+    private WebAuthnRegistrationContext context;
+    
+    private RegisteredCredential credential;
+    
+    private CredentialRegistration reg;
+    
+    @Override
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        context = addWebAuthnRegistrationContext();
+        
+        action = new DeletePublicKeyCredential();
+        action.setWebAuthnClient(client);
+        
+        mockAuthenticator = new MockAuthenticator(RPID);
+        
+        final var user = UserIdentity.builder()
+                .name("jdoe")
+                .displayName("John Doe")
+                .id(ByteArray.fromBase64(USER_HANDLE_B64))
+                .build();
+        
+        final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64);
+        
+        final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
+                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
+                        Base64Support.decode(USER_HANDLE_B64));
+        
+        final var barray = ByteArray.fromBase64(USER_HANDLE_B64);
+        assert barray.getBase64().equals(USER_HANDLE_B64);
+        assert Arrays.equals(barray.getBytes(), Base64Support.decode(USER_HANDLE_B64));
+
+        
+        credential = RegisteredCredential.builder()
+                .credentialId(attestation.getId())
+                .userHandle(ByteArray.fromBase64(USER_HANDLE_B64))
+                .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+                        .getAttestedCredentialData().get().getCredentialPublicKey())
+                .build();
+         
+         reg = CredentialRegistration.builder()
+                 .withUserIdentity(user)
+                 .withTransports(new TreeSet<AuthenticatorTransport>())
+                 .withRegistrationTime(Instant.now())
+                 .withCredential(credential)
+                 .withAttestationMetadata(CollectionSupport.emptySet())
+                 .withCredentialNickname("nickname")
+                 .withDiscoverable(Optional.of(Boolean.TRUE))
+                 .withUserVerified(true)
+                 .build();
+        
+        credentialRepo.addRegistrationByUsername("jdoe", reg);
+        
+        action.setCredentialRepository(credentialRepo);
+    } 
+    
+    @Test
+    public void testDeleteKey() throws ComponentInitializationException {
+        context.setUsername("jdoe");
+        context.setCredentialIdToRemove(credential.getCredentialId().getBytes());
+        action.initialize();
+        
+        final Event event = action.execute(src);
+        assertNull(event);
+        // Was it removed
+        assertEquals(credentialRepo.getCredentialIdsForUsername("jdoe").size(), 0);
+    }
+    
+    @Test
+    public void testDeleteKey_NoCredentialIdInContext() throws ComponentInitializationException {
+        context.setUsername("jdoe");
+        //context.setCredentialIdToRemove(credential.getCredentialId().getBytes());
+        action.initialize();
+        
+        final Event event = action.execute(src);
+        assertNotNull(event);
+        assert event != null;
+        assertEquals(event.getId(), WebAuthnRegistrationEventIds.INVALID_REGISTRATION_CTX);
+    }
+    
+    @Test
+    public void testDeleteKey_NoUsernameInContext() throws ComponentInitializationException {
+        //context.setUsername("jdoe");
+        context.setCredentialIdToRemove(credential.getCredentialId().getBytes());
+        action.initialize();
+        
+        final Event event = action.execute(src);
+        assertNotNull(event);
+        assert event != null;
+        assertEquals(event.getId(), WebAuthnRegistrationEventIds.INVALID_REGISTRATION_CTX);
+    }
+    
+    @Test
+    public void testDeleteKey_NoCredentialToRemove() throws ComponentInitializationException {
+        
+        // Remove the credential
+        credentialRepo.removeRegistrationByUsername("jdoe", reg);
+        
+        context.setUsername("jdoe");
+        
+        // Try removing one that no longer exists
+        context.setCredentialIdToRemove(credential.getCredentialId().getBytes());
+        action.initialize();
+        
+        // No error
+        final Event event = action.execute(src);
+        assertNull(event);
+        // Still not there
+        assertEquals(credentialRepo.getCredentialIdsForUsername("jdoe").size(), 0);
+    }
+    
+    @Test
+    public void testDeleteKey_CrentialNotFound() throws ComponentInitializationException {
+        context.setUsername("jdoe");
+        // Fake a credential ID that has not been registered
+        context.setCredentialIdToRemove(new byte[] {(byte)0xFF});
+        action.initialize();
+        
+        final Event event = action.execute(src);
+        assertNull(event);
+        // Nothing was removed
+        assertEquals(credentialRepo.getCredentialIdsForUsername("jdoe").size(), 1);
+    }
+
+}
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/GenerateServerChallengeTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/GenerateServerChallengeTest.java
new file mode 100644
index 0000000..8cc7ddb
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/GenerateServerChallengeTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.util.Random;
+
+import org.opensaml.profile.action.EventIds;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Tests for {@link GenerateServerChallenge}
+ */
+public class GenerateServerChallengeTest extends AbstractWebAuthnTest {
+    
+    private GenerateServerChallenge action;
+    
+    private BaseWebAuthnContext context;
+    
+    @Override
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        context = addBaseWebAuthnRegistrationContext();
+        
+        action = new GenerateServerChallenge();
+        action.setWebAuthnClient(client);
+        action.setCredentialRepository(credentialRepo);
+    } 
+    
+    @Test
+    public void testChallengeGeneration() throws ComponentInitializationException {
+        action.initialize();
+        
+        final Event event = action.execute(src);
+        assertNull(event);
+        assertNotNull(context.getServerChallenge());
+    }
+    
+    @SuppressWarnings("null")
+    @Test
+    public void testChallengeGeneration_CustomStrategy() throws ComponentInitializationException {
+        action.setChallengeGeneratorStrategy(prc -> {
+            final Random random = new Random();
+            final byte[] challenge = new byte[64];
+            random.nextBytes(challenge);
+            return challenge;
+        });
+        action.initialize();
+        
+        final Event event = action.execute(src);
+        assertNull(event);
+        assertNotNull(context.getServerChallenge());
+        assertEquals(context.getServerChallenge().length, 64);
+    }
+    
+    @SuppressWarnings("null")
+    @Test
+    public void testChallengeGeneration_CustomStrategy_Null() throws ComponentInitializationException {
+        action.setChallengeGeneratorStrategy(prc -> null);
+        action.initialize();
+        
+        final Event event = action.execute(src);
+        assertNotNull(event);
+        assertEquals(event.getId(), EventIds.INVALID_PROFILE_CTX);
+
+    }
+
+}
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
new file mode 100644
index 0000000..0ab06b7
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeSet;
+
+import org.opensaml.profile.action.EventIds;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.yubico.webauthn.RegisteredCredential;
+import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
+import com.yubico.webauthn.data.AuthenticatorTransport;
+import com.yubico.webauthn.data.ByteArray;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
+import com.yubico.webauthn.data.UserIdentity;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Tests for {@link LookupRegisteredCredentials}
+ */
+public class LookupRegisteredCredentialsTest extends AbstractWebAuthnTest {
+    
+    private LookupRegisteredCredentials action;
+    
+    private BaseWebAuthnContext context;
+    
+    private RegisteredCredential credential;
+    
+    private CredentialRegistration reg;
+    
+    @Override
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        context = addBaseWebAuthnRegistrationContext();
+        
+        mockAuthenticator = new MockAuthenticator(RPID);
+        
+        final var user = UserIdentity.builder()
+                .name("jdoe")
+                .displayName("John Doe")
+                .id(ByteArray.fromBase64(USER_HANDLE_B64))
+                .build();
+        
+        final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64);
+        
+        final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
+                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
+                        Base64Support.decode(USER_HANDLE_B64));
+        
+        final var barray = ByteArray.fromBase64(USER_HANDLE_B64);
+        assert barray.getBase64().equals(USER_HANDLE_B64);
+        assert Arrays.equals(barray.getBytes(), Base64Support.decode(USER_HANDLE_B64));
+
+        
+        credential = RegisteredCredential.builder()
+                .credentialId(attestation.getId())
+                .userHandle(ByteArray.fromBase64(USER_HANDLE_B64))
+                .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+                        .getAttestedCredentialData().get().getCredentialPublicKey())
+                .build();
+         
+         reg = CredentialRegistration.builder()
+                 .withUserIdentity(user)
+                 .withTransports(new TreeSet<AuthenticatorTransport>())
+                 .withRegistrationTime(Instant.now())
+                 .withCredential(credential)
+                 .withAttestationMetadata(CollectionSupport.emptySet())
+                 .withCredentialNickname("nickname")
+                 .withDiscoverable(Optional.of(Boolean.TRUE))
+                 .withUserVerified(true)
+                 .build();
+        
+        credentialRepo.addRegistrationByUsername("jdoe", reg);       
+        
+        action = new LookupRegisteredCredentials();
+        action.setWebAuthnClient(client);
+        action.setCredentialRepository(credentialRepo);
+    } 
+    
+    @Test
+    public void testLookup() throws ComponentInitializationException {        
+        context.setUsername("jdoe");
+        action.initialize();        
+        final Event event = action.execute(src);
+        assertNull(event);
+        
+        assertEquals(context.getExistingCredentials().size(), 1);
+    }
+    
+    @Test
+    public void testLookup_NotFound() throws ComponentInitializationException {        
+        context.setUsername("not-found");
+        action.initialize();        
+        final Event event = action.execute(src);
+        assertNull(event);
+        
+        assertEquals(context.getExistingCredentials().size(), 0);
+    }
+    
+    @SuppressWarnings("null")
+    @Test
+    public void testLookup_NoUsername() throws ComponentInitializationException {        
+        action.initialize();        
+        final Event event = action.execute(src);
+        assertNotNull(event);
+        assertEquals(event.getId(),  EventIds.INVALID_PROFILE_CTX);
+    }
+
+}
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
index 56993e0..0c01fa8 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
@@ -194,6 +194,28 @@ public class MockAuthenticator {
     /**
      * Create a WebAuthn public key credential assertion response. A new response is created each time.
      * 
+     * <p>The assertion signature is created using the credentials private key. This is not to be confused with the
+     * attestation signature, which is different than an assertion signature. From the spec:</p>
+     * 
+     *  <ul>
+     *  <li>An attestation signature is produced when a new public key credential is created via an 
+     *  authenticatorMakeCredential operation. An attestation signature provides cryptographic proof of certain 
+     *  properties of the authenticator and the credential. For instance, an attestation signature asserts the 
+     *  authenticator type (as denoted by its AAGUID) and the credential public key. The attestation signature is 
+     *  signed by an attestation private key, which is chosen depending on the type of attestation desired. 
+     *  For more details on attestation, see § 6.5 Attestation.</li>
+     *  
+     *  <li>
+     *  An assertion signature is produced when the authenticatorGetAssertion method is invoked. It represents an 
+     *  assertion by the authenticator that the user has consented to a specific transaction, such as logging in, 
+     *  or completing a purchase. Thus, an assertion signature asserts that the authenticator possessing a particular 
+     *  credential private key has established, to the best of its ability, that the user requesting this transaction 
+     *  is the same user who consented to creating that particular public key credential. It also asserts additional 
+     *  information, termed client data, that may be useful to the caller, such as the means by which user consent was 
+     *  provided, and the prompt shown to the user by the authenticator.</li>
+     *  </ul>
+     *  
+     * 
      * @param credentialId the credential identifier to give this credential, should be one that is registered.
      * @param clientData the client data passed by the 'client' (browser) during registration.
      * 

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list