[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