[java-idp-plugin-webauthn] branch main updated: Generalise interfaces. Improve tests and test mocks

Phil Smart philip.smart at jisc.ac.uk
Thu Nov 23 17:40:33 UTC 2023


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=d45bad9970c28bf061be253cdbbf73be74dc6682

The following commit(s) were added to refs/heads/main by this push:
     new d45bad9  Generalise interfaces. Improve tests and test mocks
d45bad9 is described below

commit d45bad9970c28bf061be253cdbbf73be74dc6682
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Nov 23 17:40:25 2023 +0000

    Generalise interfaces. Improve tests and test mocks
---
 .../webauthn/WebAuthnAuthenticationClient.java     |  46 +-
 ... => WebAuthnAuthenticationClientException.java} |  10 +-
 .../WebauthnAuthenticationClientFactory.java       |   4 +-
 .../context/WebAuthnAuthenticationContext.java     |  90 ++--
 .../impl/YubicoWebauthnAuthenticationClient.java   | 113 +++--
 .../CreatePublicKeyCredentialCreationOptions.java  |  21 +-
 .../CreatePublicKeyCredentialRequestOptions.java   |  28 +-
 .../ExtractPublicKeyAssertionFromFormRequest.java  |  29 +-
 .../ExtractPublicKeyCredentialFromFormRequest.java |  37 +-
 ... ValidateAuthenticatorAttestationResponse.java} |  30 +-
 .../webauthn/impl/ValidateWebAuthnAssertion.java   |  76 +++-
 .../storage/impl/CredentialRegistration.java       |  21 +-
 .../webauthn-registration-beans.xml                |   4 +-
 .../idp/flows/authn/WebAuthn/webauthn-beans.xml    |  15 +-
 .../YubicoWebauthnAuthenticationClientTest.java    | 290 +++++++++++--
 .../authn/webauthn/impl/AbstractWebAuthnTest.java  |  30 +-
 .../authn/webauthn/impl/MockAuthenticator.java     | 480 +++++++++++++++++----
 .../impl/ValidatePublicKeyCredentialTest.java      |  34 +-
 18 files changed, 992 insertions(+), 366 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java
index f4e5ff1..bbcbc5d 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java
@@ -1,12 +1,9 @@
 package net.shibboleth.idp.plugin.authn.webauthn;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 
-import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
-import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
-import com.yubico.webauthn.data.PublicKeyCredential;
-
 /**
  * A client that manages the entire webauthn authentication and registration ceremony.
  * 
@@ -23,11 +20,17 @@ public interface WebAuthnAuthenticationClient {
       * @param username the username of the username that has been pre-identified. Can be null
       *                 if no user has been identified, and the IdP is requesting the client discover
       *                 the credential.  
-      * @return a JSON serialized PublicKeyCredentialRequestOptions object. Can be null if
-      *         one could not be created.
+      * @param userHandle an opaque user.id used to map public key credentials to user accounts and vice-versa.
+      * @param the challenge used when creating new credentials
+      * 
+      * @return a JSON serialized PublicKeyCredentialRequestOptions object. 
+      * 
+      * @throws WebAuthnAuthenticationClientException if there is an error generating the authentication request
+      *         
       */
     //TODO how do we guarantee this is JSON, or just return the Yubico object?
-     @Nullable String createAuthenticationRequest(@Nullable final String username);
+     @Nonnull String createAuthenticationRequest(@Nullable final String username, @Nullable final byte[] userHandle, 
+             @Nonnull final byte[] challenge) throws WebAuthnAuthenticationClientException;
      
      /**
       * Create a JSON serialized PublicKeyCredentialCreationOptions.
@@ -40,30 +43,41 @@ public interface WebAuthnAuthenticationClient {
       * 
       * @return a JSON serialized PublicKeyCredentialCreationOptions object. Can be {@code null} if
       *         one could not be created.
+      *         
+      * @throws WebAuthnAuthenticationClientException if there is an error generating the creation request
       */
     //TODO how do we guarantee this is JSON, or just return the Yubico object?
-     @Nullable String createRegistrationRequest(@Nullable final String username, final byte[] userHandle, final byte[] challenge);
+     @Nullable String createRegistrationRequest(@Nullable final String username, @Nullable final byte[] userHandle, 
+             @Nonnull final byte[] challenge) throws WebAuthnAuthenticationClientException;
      
      
      /**
       * Validate the Authenticator Assertion Response. 
       * 
-      * @param jsonAssertionResponse the JSON representation of the assertion response.
+      * @param usernmae ....TODO
+      * @param userHandle ....TODO
+      * @param publicKeyCredentialRequestOptions the options used when generating an assertion for authentication.
+      * @param authenticatorAssertionResponse the JSON representation of the assertion response.
       * 
       * @return true if the assertion was verified successfully, false otherwise.
+      * 
+      * @throws WebAuthnAuthenticationClientException if there is an error during validation
       */
-     boolean validateAuthenticatorAssertionResponse(@Nullable final String jsonAssertionResponse);
+     boolean validateAuthenticatorAssertionResponse(@Nullable final String username, 
+             @Nullable final byte[] userHandle, @Nonnull final String publicKeyCredentialRequestOptions, 
+             @Nonnull final String authenticatorAssertionResponse) 
+             throws WebAuthnAuthenticationClientException;
 
 
     /**
      * Validate a registration request.
      * 
-     * @param request
-     * @param response
-     * @return 
+     * @param publicKeyCredentialCreationOptions the options used when requesting a new public key credential
+     * @param authenticatorAttestationResponse the response to the client's request to create a public key credential
+     * 
+     * @return true if the registration is valid, false otherwise.
      */
-     //TODO use other validation method?
-     boolean validateRegistration(String request,
-            PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> response);       
+     boolean validateRegistration(@Nonnull final String publicKeyCredentialCreationOptions, 
+             @Nonnull final String authenticatorAttestationResponse);       
 
 }
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebauthnAuthenticationClientException.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClientException.java
similarity index 73%
rename from webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebauthnAuthenticationClientException.java
rename to webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClientException.java
index 7e79120..0a876a5 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebauthnAuthenticationClientException.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClientException.java
@@ -3,7 +3,7 @@ package net.shibboleth.idp.plugin.authn.webauthn;
 /**
  *  An exception to signal an error condition during execution or creation of a Webauthn client.
  */
-public class WebauthnAuthenticationClientException extends Exception {
+public class WebAuthnAuthenticationClientException extends Exception {
     
     /** Serial UID. */
     private static final long serialVersionUID = -2380145079984333546L;
@@ -12,7 +12,7 @@ public class WebauthnAuthenticationClientException extends Exception {
      * Constructor.
      *
      */
-    public WebauthnAuthenticationClientException() {
+    public WebAuthnAuthenticationClientException() {
         super();
         
     }
@@ -23,7 +23,7 @@ public class WebauthnAuthenticationClientException extends Exception {
      * @param message exception message
      * @param cause exception to be wrapped by this one
      */
-    public WebauthnAuthenticationClientException(final String message, final Throwable cause) {
+    public WebAuthnAuthenticationClientException(final String message, final Throwable cause) {
         super(message, cause);
         
     }
@@ -33,7 +33,7 @@ public class WebauthnAuthenticationClientException extends Exception {
      * 
      * @param message exception message
      */
-    public WebauthnAuthenticationClientException(final String message) {
+    public WebAuthnAuthenticationClientException(final String message) {
         super(message);
         
     }
@@ -43,7 +43,7 @@ public class WebauthnAuthenticationClientException extends Exception {
      * 
      * @param cause exception to be wrapped by this one
      */
-    public WebauthnAuthenticationClientException(final Throwable cause) {
+    public WebAuthnAuthenticationClientException(final Throwable cause) {
         super(cause);
         
     }
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebauthnAuthenticationClientFactory.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebauthnAuthenticationClientFactory.java
index 4968181..b0193b6 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebauthnAuthenticationClientFactory.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebauthnAuthenticationClientFactory.java
@@ -13,8 +13,8 @@ public interface WebauthnAuthenticationClientFactory {
      * 
      * @return the client, never {@code nul}.
      * 
-     * @throws WebauthnAuthenticationClientException if there is an errtor creating the client.
+     * @throws WebAuthnAuthenticationClientException if there is an errtor creating the client.
      */
-    @Nonnull WebAuthnAuthenticationClient createInstance() throws WebauthnAuthenticationClientException;
+    @Nonnull WebAuthnAuthenticationClient createInstance() throws WebAuthnAuthenticationClientException;
 
 }
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnAuthenticationContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnAuthenticationContext.java
index 9eb0209..83a9f9b 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnAuthenticationContext.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnAuthenticationContext.java
@@ -6,11 +6,6 @@ import javax.annotation.concurrent.NotThreadSafe;
 
 import org.opensaml.messaging.context.BaseContext;
 
-import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
-import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
-import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
-import com.yubico.webauthn.data.PublicKeyCredential;
-
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.EncodingException;
 import net.shibboleth.shared.logic.Constraint;
@@ -21,11 +16,7 @@ import net.shibboleth.shared.logic.Constraint;
 public final class WebAuthnAuthenticationContext extends BaseContext {
     
     @Nullable private byte[] serverChallenge;  
-    
-    /** A registration public key credential.*/   
-    @Nullable 
-    private PublicKeyCredential<AuthenticatorAttestationResponse,ClientRegistrationExtensionOutputs> publicKeyCredential;
-    
+        
     /** The  credential public key encoded in COSE_Key format.*/
     @Nullable private byte[] publicKey;
     
@@ -39,16 +30,19 @@ public final class WebAuthnAuthenticationContext extends BaseContext {
     @Nullable private byte[] existingCredentialId;
     
     /** In a new context? TODO. The existing credential public key encoded in COSE_Key format if found.*/
-    @Nullable private byte[] existingPublicKey;
+    @Nullable private byte[] existingPublicKey;    
+    
+    /** An assertion response that is the result of an authentication.*/
+    @Nullable private String authenticatorAssertionResponse;
     
-    //TODO would need to be bytes if Yubico agnostic (if we wanted that)
     /** An assertion response that is the result of an authentication.*/
-    @Nullable 
-    private PublicKeyCredential<AuthenticatorAssertionResponse,ClientRegistrationExtensionOutputs>  assertionResponse;
+    @Nullable private String authenticatorAttestationResponse;
     
     /** The public key credential creation options for registration.*/ 
     @Nullable private String publicKeyCredentialCreationOptions;
    
+    /** The public key credential request options for authentication.*/ 
+    @Nullable private String publicKeyCredentialRequestOptions;
     
     /**
      * Set the server challenge which the client authenticator needs to sign.
@@ -63,21 +57,23 @@ public final class WebAuthnAuthenticationContext extends BaseContext {
         serverChallenge = challenge;
         return this;
     }
-    
+        
     /**
-     * Get the assertion response as a result of authentication.
+     * Get the attestation response as a result of creating a new credential.
      * 
-     * @return the assertion.
+     * @return Returns the authenticator attestation response.
      */
-    @Nullable public PublicKeyCredential<AuthenticatorAssertionResponse, ClientRegistrationExtensionOutputs>
-            getAssertionResponse() {
-        return assertionResponse;
+    @Nullable public String getAuthenticatorAttestationResponse() {
+        return authenticatorAttestationResponse;
     }
     
-    @Nonnull public WebAuthnAuthenticationContext setAssertionResponse(@Nullable final
-            PublicKeyCredential<AuthenticatorAssertionResponse, ClientRegistrationExtensionOutputs> assertion) {
-        assertionResponse = assertion;
-        return this;
+    /**
+     * Set the attestation response as a result of creating a new credential.
+     * 
+     * @param authenticatorAttestationResponse The authenticatorAttestationResponse to set.
+     */
+    public void setAuthenticatorAttestationResponse(@Nullable final String attestation) {
+        authenticatorAttestationResponse = attestation;
     }
     
     /**
@@ -187,17 +183,6 @@ public final class WebAuthnAuthenticationContext extends BaseContext {
         return this;
     }
     
-    @Nullable 
-    public PublicKeyCredential<AuthenticatorAttestationResponse,ClientRegistrationExtensionOutputs> getPublicKeyCredential() {
-        return publicKeyCredential;
-    }
-    
-    // From creation of a credential
-    public void setPublicKeyCredential(
-            @Nonnull final PublicKeyCredential<AuthenticatorAttestationResponse,ClientRegistrationExtensionOutputs> credential) {
-        publicKeyCredential = Constraint.isNotNull(credential, "Public Key Credential can not be null or empty");
-    }
-    
     //TODO throw in a context, this could be null (which is bad here?)
     @SuppressWarnings("null")
     @Nullable public String getServerChallengeBase64() throws EncodingException {
@@ -222,5 +207,40 @@ public final class WebAuthnAuthenticationContext extends BaseContext {
     @Nullable public String getPublicKeyCredentialCreationOptions() {
         return publicKeyCredentialCreationOptions;
     }
+    
+    /**
+     * Set the public key credential request options for authentication.
+     * 
+     * @param the credential request options to set.
+     */
+    public void setPublicKeyCredentialRequestOptions(@Nullable final String options) {
+        publicKeyCredentialRequestOptions = options;
+    }
+    
+    /**
+     * Get the public key credential request options.
+     * 
+     * @return the public key credential request options.
+     */
+    @Nullable public String getPublicKeyCredentialRequestOptions() {
+        return publicKeyCredentialRequestOptions;
+    }
 
+    /**
+     * Set the raw authenticator assertion response, returned after the authentication ceremony.
+     *  
+     * @param assertion The authenticator assertion response to set.
+     */
+    public void setAuthenticatorAssertionResponse(@Nullable final String assertion) {
+        authenticatorAssertionResponse = assertion;
+    }
+    
+    /**
+     * Get the authenticator assertion response, returned after the authentication ceremony.
+     * 
+     * @return the authenticator a ssertion response.
+     */
+    @Nullable public String getAuthenticatorAssertionResponse() {
+        return authenticatorAssertionResponse;
+    }
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java
index beaa84c..f78bad6 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.client.impl;
 
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -28,25 +29,28 @@ import org.slf4j.Logger;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.yubico.webauthn.AssertionRequest;
+import com.yubico.webauthn.FinishAssertionOptions;
 import com.yubico.webauthn.FinishRegistrationOptions;
 import com.yubico.webauthn.RelyingParty;
-import com.yubico.webauthn.StartAssertionOptions;
-import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
 import com.yubico.webauthn.data.ByteArray;
-import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
 import com.yubico.webauthn.data.PublicKeyCredential;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
 import com.yubico.webauthn.data.PublicKeyCredentialParameters;
+import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
 import com.yubico.webauthn.data.UserIdentity;
 import com.yubico.webauthn.data.UserVerificationRequirement;
+import com.yubico.webauthn.exception.AssertionFailedException;
 import com.yubico.webauthn.exception.RegistrationFailedException;
 
 import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClient;
+import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClientException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
- * Yuibico version of the {@link WebAuthnAuthenticationClient}.
+ * Yuibico implementation of a {@link WebAuthnAuthenticationClient}.
  * 
  * <p>Thread-safe, only a single instance is required.</p>
  */
@@ -88,7 +92,8 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
     }
 
     @Override
-    public String createAuthenticationRequest(@Nullable final String username) {
+    public String createAuthenticationRequest(@Nullable final String username, final byte[] userHandle, 
+            final byte[] challenge) throws WebAuthnAuthenticationClientException {
         try {
             //set default to preferred.
             UserVerificationRequirement userVerificationRequirement = UserVerificationRequirement.PREFERRED;
@@ -96,22 +101,44 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
                 //then require user verification? makes sense, but is that part of the spec?
                 userVerificationRequirement = UserVerificationRequirement.REQUIRED;
             }
-            final AssertionRequest assertion =
-                    rp.startAssertion(StartAssertionOptions.builder()
-                            .username(Optional.ofNullable(username))
-                            .userVerification(userVerificationRequirement)
-                            .build());
-            return assertion.toJson();
+//            final AssertionRequest assertion =
+//                    rp.startAssertion(StartAssertionOptions.builder()
+//                            .username(Optional.ofNullable(username))
+//                            .userVerification(userVerificationRequirement)
+//                            .build());
+            final PublicKeyCredentialRequestOptions pkcro =
+                    PublicKeyCredentialRequestOptions.builder()
+                        .challenge(new ByteArray(challenge))
+                        .rpId(rp.getIdentity().getId())
+//                        .allowCredentials(
+//                            OptionalUtil.orElseOptional(
+//                                    startAssertionOptions.getUsername(),
+//                                    () ->
+//                                        startAssertionOptions
+//                                            .getUserHandle()
+//                                            .flatMap(credentialRepository::getUsernameForUserHandle))
+//                                .map(
+//                                    un ->
+//                                        new ArrayList<>(credentialRepository.getCredentialIdsForUsername(un))))
+//                        .extensions(
+//                            startAssertionOptions
+//                                .getExtensions()
+//                                .merge(startAssertionOptions.getExtensions().toBuilder().appid(appId).build()))
+                        .userVerification(userVerificationRequirement)
+                        .timeout(Optional.of(60000l))
+                        .build();
+            
+            return om.writerWithDefaultPrettyPrinter().writeValueAsString(pkcro);
         }
         catch (final JsonProcessingException e) {
-            log.error("Could not construct FIDO2 authentication request for '{}'",username,e);
+            throw new WebAuthnAuthenticationClientException(e);
         }        
-        return null;
     }
     
     /** {@inheritDoc} */
     @Override
-    public String createRegistrationRequest(final String username, final byte[] userHandle, final byte[] challenge) {
+    public String createRegistrationRequest(final String username, final byte[] userHandle, final byte[] challenge) 
+            throws WebAuthnAuthenticationClientException {
         try {
             //set default to preferred.
             UserVerificationRequirement userVerificationRequirement = UserVerificationRequirement.PREFERRED;
@@ -145,35 +172,68 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
             return om.writerWithDefaultPrettyPrinter().writeValueAsString(options);
         }
         catch (final JsonProcessingException e) {
-            log.error("Could not construct FIDO2 authentication request for '{}'",username,e);
+            throw new WebAuthnAuthenticationClientException(e);
         }        
-        return null;
     }
 
     @Override
-    public boolean validateAuthenticatorAssertionResponse(@Nullable final String jsonAssertionResponse) {
-       
-        if (jsonAssertionResponse == null || jsonAssertionResponse.isEmpty()) {
-            log.warn("JSON Assertion Response is either null or empty, authentication can not be validated");
+    public boolean validateAuthenticatorAssertionResponse(@Nullable final String username, 
+            @Nullable final byte[] userHandle,
+            @Nonnull final String publicKeyCredentialRequestOptions,
+            @Nonnull final String authenticatorAssertionResponse) throws WebAuthnAuthenticationClientException {
+        
+        try {
+            
+            final PublicKeyCredentialRequestOptions request = 
+                    om.readValue(publicKeyCredentialRequestOptions, PublicKeyCredentialRequestOptions.class);
+            
+            final AssertionRequest requestAssertion = AssertionRequest.builder()
+                .publicKeyCredentialRequestOptions(request)
+                // TODO these
+                .userHandle(Optional.ofNullable(userHandle != null ? new ByteArray(userHandle) : null))
+                .username(Optional.ofNullable(username))
+                .build();
+            
+            final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkCred = 
+                    PublicKeyCredential.parseAssertionResponseJson(authenticatorAssertionResponse);
+            log.trace("Client Data '{}'",pkCred.getResponse().getClientData());
+            log.trace("Signature '{}'",pkCred.getResponse().getSignature());
+            rp.finishAssertion(FinishAssertionOptions.builder()
+                    .request(requestAssertion)
+                    .response(pkCred)
+                    .build());
+            
+        }  catch (final AssertionFailedException e) {
+            log.error("Public key assertion (authentication) is not valid", e);
             return false;
+        } catch (final Exception e) {           
+            throw new WebAuthnAuthenticationClientException(e);            
         }
+                
         return true;
     }
 
     /** {@inheritDoc} */
     @Override
-    public boolean validateRegistration(final String request,
-            final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> response) {
+    public boolean validateRegistration(final String publicKeyCredentialCreationOptions, 
+            final String authenticatorAttestationResponse) {
         
         try {
             final PublicKeyCredentialCreationOptions requestOptions = 
-                    PublicKeyCredentialCreationOptions.fromJson(request);
+                    PublicKeyCredentialCreationOptions.fromJson(publicKeyCredentialCreationOptions);
+            
+            log.debug("Public Key Credential to validate '{}'", authenticatorAttestationResponse);
+            final var publicKeyRegistration = 
+                    PublicKeyCredential.parseRegistrationResponseJson(authenticatorAttestationResponse);
+            
+            
             rp.finishRegistration(FinishRegistrationOptions.builder()
                     .request(requestOptions)
-                    .response(response)
+                    .response(publicKeyRegistration)
                     .build());
-        } catch (final RegistrationFailedException | JsonProcessingException e) {
-           log.error("Public key credential can not be registered", e);
+            // TODO do we need the result?
+        } catch (final RegistrationFailedException | IOException e) {
+           log.error("Public key attestation (registration) is not valid", e);
            // Should throw this?
            return false;
         }
@@ -182,4 +242,5 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
 
 
 
+
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialCreationOptions.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialCreationOptions.java
index 8bec478..df58513 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialCreationOptions.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialCreationOptions.java
@@ -28,6 +28,7 @@ import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnAuthenticationAction;
 import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClient;
+import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClientException;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -45,20 +46,20 @@ public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnAu
             @Nonnull final WebAuthnAuthenticationContext context) {
         
         final WebAuthnAuthenticationClient client = getWebAuthnClient();
-        if (client == null) {
-            log.error("{} WebAuthn client is null, has the context been created correctly?",getLogPrefix());
+        final byte[] challenge = context.getServerChallenge();
+        if (challenge == null) {
+            log.error("{} WebAuthn challenge is null, has the context been created correctly?",getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
             return;
         }
         
         try {                
-        final String pkCredCreationOptions = 
-                client.createRegistrationRequest(context.getUsername(), generateUserHandle(), 
-                        context.getServerChallenge());
-                //verify correct JSON response?
-                context.setPublicKeyCredentialCreationOptions(pkCredCreationOptions);
-                log.debug("Created PublicKeyCredentialCreationOptions '{}'",pkCredCreationOptions);
-        } catch (final NoSuchAlgorithmException e) {
+            final String pkCredCreationOptions = 
+                    client.createRegistrationRequest(context.getUsername(), generateUserHandle(), challenge);
+            //verify correct JSON response?
+            context.setPublicKeyCredentialCreationOptions(pkCredCreationOptions);
+            log.debug("{} Created PublicKeyCredentialCreationOptions '{}'",getLogPrefix(), pkCredCreationOptions);
+        } catch (final NoSuchAlgorithmException | WebAuthnAuthenticationClientException e) {
             log.error("{} Unable to generate PublicKeyCredentialCreationOptions",getLogPrefix(), e);
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
             return;
@@ -76,7 +77,7 @@ public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnAu
     @Nonnull private byte[] generateUserHandle() throws NoSuchAlgorithmException {        
         final byte[] bytes = new byte[32];
         SecureRandom.getInstanceStrong().nextBytes(bytes);
-        log.trace("Generated '{}' byte challenge",bytes.length);
+        log.trace("{} Generated '{}' byte challenge",getLogPrefix(), bytes.length);
         return bytes;
     }
 
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
index 1068f35..12ed08e 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
@@ -25,6 +25,7 @@ import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnAuthenticationAction;
 import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClient;
+import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClientException;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -41,18 +42,29 @@ public class CreatePublicKeyCredentialRequestOptions extends AbstractWebAuthnAut
             @Nonnull final AuthenticationContext authenticationContext, 
             @Nonnull final WebAuthnAuthenticationContext context) {
         
-        final WebAuthnAuthenticationClient client = getWebAuthnClient();
-        if (client == null) {
-            log.error("{} WebAuthn client is null, has the context been created correctly?",getLogPrefix());
+        final WebAuthnAuthenticationClient client = getWebAuthnClient();  
+        
+        final byte[] challenge = context.getServerChallenge();
+        if (challenge == null) {
+            log.error("{} WebAuthn challenge is null, has the context been created correctly?",getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
             return;
         }
                 
-        final String pkCredRequestOptions = client.createAuthenticationRequest(context.getUsername());
-        //verify correct JSON response?
-        
-        
-        log.debug("Created PublicKeyCredentialRequestOptions '{}'",pkCredRequestOptions);
+        try {
+            //TODO userhandle needs to be pulled out.
+            final String pkCredRequestOptions = client.createAuthenticationRequest(context.getUsername(), 
+                    null, challenge);
+            //verify correct JSON response?
+            context.setPublicKeyCredentialRequestOptions(pkCredRequestOptions);
+            
+            log.debug("{} Created PublicKeyCredentialRequestOptions: '{}'",getLogPrefix(), pkCredRequestOptions);
+        } catch (final WebAuthnAuthenticationClientException e) {
+            log.error("{} Unable to generate PublicKeyCredentialRequestOptions",getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
+            return;
+        }
+       
         
     }
 
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyAssertionFromFormRequest.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyAssertionFromFormRequest.java
index 859e2b3..e55c766 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyAssertionFromFormRequest.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyAssertionFromFormRequest.java
@@ -24,12 +24,7 @@ import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
-import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
-import com.yubico.webauthn.data.PublicKeyCredential;
 
 import jakarta.servlet.http.HttpServletRequest;
 import net.shibboleth.idp.authn.AuthnEventIds;
@@ -45,7 +40,7 @@ import net.shibboleth.shared.primitive.StringSupport;
 
 
 /**
- * An action that derives the PublicKeyCredential from a form parameter.
+ * An action that derives the PublicKeyAssertion from a form parameter.
  */
 public class ExtractPublicKeyAssertionFromFormRequest extends AbstractWebAuthnAuthenticationAction {
 
@@ -108,30 +103,18 @@ public class ExtractPublicKeyAssertionFromFormRequest extends AbstractWebAuthnAu
             return;
         }
         
-        final String pkCredJson = extractPublicKeyCredential(request);   
+        final String pkCredJson = extractAuthenticatorResponse(request);   
         log.trace("Public Key Assertion in JSON is '{}'",pkCredJson);
         if (pkCredJson == null) {
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             return;
-        }
-                
-        try {
-            final PublicKeyCredential<AuthenticatorAssertionResponse,ClientRegistrationExtensionOutputs> pkCred = 
-                    objectMapper.readValue(pkCredJson,
-                            new TypeReference<PublicKeyCredential<AuthenticatorAssertionResponse,ClientRegistrationExtensionOutputs>>() {});
-            log.debug("Client Data '{}'",pkCred.getResponse().getClientData());
-            log.debug("Signature '{}'",pkCred.getResponse().getSignature());
-            context.setAssertionResponse(pkCred);
-        } catch (final JsonProcessingException e) {
-            log.debug("{} Can not extract public key credential response", getLogPrefix(),e);
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-            return;
-        }
-        
+        }                
        
+        context.setAuthenticatorAssertionResponse(pkCredJson);
+
     }
 
-    @Nullable private String extractPublicKeyCredential(@Nonnull final HttpServletRequest httpRequest) {
+    @Nullable private String extractAuthenticatorResponse(@Nonnull final HttpServletRequest httpRequest) {
         return httpRequest.getParameter(fieldName);
     }
     
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyCredentialFromFormRequest.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyCredentialFromFormRequest.java
index f1211f4..2d893da 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyCredentialFromFormRequest.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyCredentialFromFormRequest.java
@@ -24,12 +24,7 @@ import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
-import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
-import com.yubico.webauthn.data.PublicKeyCredential;
 
 import jakarta.servlet.http.HttpServletRequest;
 import net.shibboleth.idp.authn.AuthnEventIds;
@@ -109,29 +104,25 @@ public class ExtractPublicKeyCredentialFromFormRequest extends AbstractWebAuthnA
             return;
         }
         
-        final String pkCredJson = extractPublicKeyCredential(request);   
-        log.trace("Public Key Credential in JSON is '{}'",pkCredJson);
-        if (StringSupport.trimOrNull(pkCredJson) == null) {
+        final String pkCredAttestationJson = extractPublicKeyCredential(request);   
+        log.trace("Public Key Credential AuthenticatorAttestationResponse in JSON is '{}'",pkCredAttestationJson);
+        if (StringSupport.trimOrNull(pkCredAttestationJson) == null) {
+            log.warn("{} Could not extract AuthenticatorAttestationResponse from form", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             return;
         }
-                
-        try {
-            final PublicKeyCredential<AuthenticatorAttestationResponse,ClientRegistrationExtensionOutputs> pkCred = 
-                    objectMapper.readValue(pkCredJson,
-                            new TypeReference<PublicKeyCredential<AuthenticatorAttestationResponse,ClientRegistrationExtensionOutputs>>() {});
-            log.debug("Client Data '{}'",pkCred.getResponse().getClientData());
-            log.debug("Attestation Data '{}'",pkCred.getResponse().getAttestation());
-            context.setPublicKeyCredential(pkCred);
-        } catch (final JsonProcessingException e) {
-            log.debug("{} Can not extract public key credential response", getLogPrefix(),e);
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-            return;
-        }
-        
-       
+
+        context.setAuthenticatorAttestationResponse(pkCredAttestationJson);
+
     }
 
+    /**
+     * Extract the public key credential from the AuthenticatorAttestationResponse in the form.
+     * 
+     * @param httpRequest the http request
+     * 
+     * @return the AuthenticationAttestationResponse JSON.
+     */
     @Nullable private String extractPublicKeyCredential(@Nonnull final HttpServletRequest httpRequest) {
         return httpRequest.getParameter(fieldName);
     }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredential.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateAuthenticatorAttestationResponse.java
similarity index 75%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredential.java
rename to webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateAuthenticatorAttestationResponse.java
index 0b7258e..cee6f8f 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredential.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateAuthenticatorAttestationResponse.java
@@ -23,11 +23,6 @@ import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
-import com.yubico.webauthn.data.ByteArray;
-import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
-import com.yubico.webauthn.data.PublicKeyCredential;
-
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnAuthenticationAction;
@@ -40,18 +35,17 @@ import net.shibboleth.shared.primitive.StringSupport;
 /**
  * Validate the public key registration attempt.
  */
-public class ValidatePublicKeyCredential extends AbstractWebAuthnAuthenticationAction {
+public class ValidateAuthenticatorAttestationResponse extends AbstractWebAuthnAuthenticationAction {
 
     /** Class logger. */
     @Nonnull
-    private final Logger log = LoggerFactory.getLogger(ValidatePublicKeyCredential.class);
+    private final Logger log = LoggerFactory.getLogger(ValidateAuthenticatorAttestationResponse.class);
     
     /** The stashed public key credential creation options used to create a new credential.*/
     @NonnullBeforeExec @NotEmpty private String pkCredCreationOptions;
     
     /** The stashed authenticator response.*/
-    @NonnullBeforeExec 
-    private PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> pkCred;
+    @NonnullBeforeExec @NotEmpty private String attestation;
 
     
     /** {@inheritDoc} */
@@ -60,8 +54,8 @@ public class ValidatePublicKeyCredential extends AbstractWebAuthnAuthenticationA
             @Nonnull final AuthenticationContext authenticationContext, 
             @Nonnull final WebAuthnAuthenticationContext context) {
        
-        pkCred = context.getPublicKeyCredential();    
-        if (pkCred == null) {
+        attestation = context.getAuthenticatorAttestationResponse();
+        if (StringSupport.trimOrNull(attestation) == null) {
             log.error("{} public key credential was null", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             return false;
@@ -84,7 +78,7 @@ public class ValidatePublicKeyCredential extends AbstractWebAuthnAuthenticationA
 
         // TODO this should throw the error?
         final boolean publicKeyCredentialIsValid = 
-                getWebAuthnClient().validateRegistration(context.getPublicKeyCredentialCreationOptions(), pkCred);
+                getWebAuthnClient().validateRegistration(pkCredCreationOptions, attestation);
         
         if (!publicKeyCredentialIsValid) {            
             log.error("{} public key credential creation options was invalid", getLogPrefix());
@@ -95,12 +89,12 @@ public class ValidatePublicKeyCredential extends AbstractWebAuthnAuthenticationA
         // If valid. Add back to context
         log.info("Public Key Registration was valid");
         //now set the credential data e.g. created public key and keyID onto the context.
-        final ByteArray credId = pkCred.getResponse().getAttestation().getAuthenticatorData()
-                .getAttestedCredentialData().get().getCredentialId();
-        final ByteArray credPublicKey = pkCred.getResponse().getAttestation().getAuthenticatorData()
-                .getAttestedCredentialData().get().getCredentialPublicKey();
-        context.setCredentialId(credId.getBytes());
-        context.setPublicKey(credPublicKey.getBytes());
+//        final ByteArray credId = pkCred.getResponse().getAttestation().getAuthenticatorData()
+//                .getAttestedCredentialData().get().getCredentialId();
+//        final ByteArray credPublicKey = pkCred.getResponse().getAttestation().getAuthenticatorData()
+//                .getAttestedCredentialData().get().getCredentialPublicKey();
+//        context.setCredentialId(credId.getBytes());
+//        context.setPublicKey(credPublicKey.getBytes());
     }
 
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
index f4985d8..e76de02 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
@@ -10,16 +10,17 @@ import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
-import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
-import com.yubico.webauthn.data.PublicKeyCredential;
-
 import net.shibboleth.idp.authn.AbstractValidationAction;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.principal.UsernamePrincipal;
+import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClient;
+import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClientException;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
@@ -36,7 +37,13 @@ public class ValidateWebAuthnAssertion extends AbstractValidationAction {
     private final Function<ProfileRequestContext, WebAuthnAuthenticationContext> webauthnContextLookupStrategy;
 
     /** The webauthn authentication context. */
-    @NonnullBeforeExec private WebAuthnAuthenticationContext context;
+    @NonnullBeforeExec private WebAuthnAuthenticationContext context;    
+    
+    /** The WebAuthn client to use.*/
+    @NonnullAfterInit private WebAuthnAuthenticationClient webAuthnClient;    
+    
+    /** The options used to create the authentication request.*/
+    @NonnullBeforeExec private String publicKeyCredentialRequestOptions;
     
     /** Constructor. */
     public ValidateWebAuthnAssertion() {
@@ -45,6 +52,27 @@ public class ValidateWebAuthnAssertion extends AbstractValidationAction {
 
     }
     
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {        
+        if (webAuthnClient == null) {
+            throw new ComponentInitializationException("WebAuthn client can not be null. Configuration error.");
+        }
+        super.doInitialize();
+    }
+    
+    
+    /**
+     * Set the WebAuthn client used to handle registration and authentication ceremonies.
+     * 
+     * @param webAuthnClient The webauthnClient to set.
+     */
+    public void setWebAuthnClient(@Nonnull final WebAuthnAuthenticationClient client) {
+        checkSetterPreconditions();
+        webAuthnClient = Constraint.isNotNull(client, "WebAuthn client can not be null");
+    }
+    
+    
     @Override
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
@@ -55,6 +83,13 @@ public class ValidateWebAuthnAssertion extends AbstractValidationAction {
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
             return false;
 
+        }
+        publicKeyCredentialRequestOptions = context.getPublicKeyCredentialRequestOptions();
+        if (publicKeyCredentialRequestOptions == null) {
+            log.warn("{} No public key credential request options in context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
+            return false;
+
         }
         return true;
     }
@@ -63,19 +98,40 @@ public class ValidateWebAuthnAssertion extends AbstractValidationAction {
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
         
-        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientRegistrationExtensionOutputs> assertion =
-                context.getAssertionResponse();
+        final String assertion = context.getAuthenticatorAssertionResponse();
+        
+        context.setUsername("not-the-username");
         
         if (assertion == null) {
-            log.warn("{} No signature was found in the assertion, {} can not authenticate ", 
+            log.warn("{} No authenticator assertion found, {} can not authenticate ", 
                     getLogPrefix(),context.getUsername());
             handleError(profileRequestContext, authenticationContext, "InvalidResponseType",
                     AuthnEventIds.INVALID_CREDENTIALS);
             recordFailure(profileRequestContext);              
             return; 
         }
-        
-        
+        try {
+            final boolean valid = webAuthnClient.validateAuthenticatorAssertionResponse(context.getUsername(), 
+                    null, publicKeyCredentialRequestOptions, assertion);
+            if (!valid) {
+                log.warn("{} Authenticator assertion was not valid for '{}', authentication failed", 
+                        getLogPrefix(),context.getUsername());
+                handleError(profileRequestContext, authenticationContext, AuthnEventIds.NO_CREDENTIALS, 
+                        AuthnEventIds.INVALID_CREDENTIALS);
+                recordFailure(profileRequestContext);              
+                return; 
+            } else {
+                log.debug("{} Authenticator assertion was valid for '{}'", getLogPrefix(), context.getUsername());
+            }
+        } catch (final WebAuthnAuthenticationClientException e) {
+            log.warn("{} Error validating authenticator assertion for '{}'", 
+                    getLogPrefix(),context.getUsername(), e);
+            handleError(profileRequestContext, authenticationContext, "InvalidResponseType",
+                    AuthnEventIds.INVALID_CREDENTIALS);
+            recordFailure(profileRequestContext);              
+            return; 
+        }
+
         buildAuthenticationResult(profileRequestContext, authenticationContext);
         
     }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistration.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistration.java
index 09c0baf..9c70e3f 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistration.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistration.java
@@ -28,16 +28,33 @@ import com.yubico.webauthn.data.UserIdentity;
 /**
  * Influenced by the CredentialRegistration class in the Yubico demo libraries.
  */
+//TODO need our own storage record, so this should be test only and then replaced with the actual one eventually
 public class CredentialRegistration {
 
     UserIdentity userIdentity;
     Optional<String> credentialNickname;
     SortedSet<AuthenticatorTransport> transports;
-
     Instant registrationTime;
     RegisteredCredential credential;
-
     Optional<Object> attestationMetadata;
+    
+    
+    public CredentialRegistration(final UserIdentity userIdentity, final Optional<String> credentialNickname,
+            final SortedSet<AuthenticatorTransport> transports, final Instant registrationTime, 
+            final RegisteredCredential credential,
+            final Optional<Object> attestationMetadata) {
+        super();
+        this.userIdentity = userIdentity;
+        this.credentialNickname = credentialNickname;
+        this.transports = transports;
+        this.registrationTime = registrationTime;
+        this.credential = credential;
+        this.attestationMetadata = attestationMetadata;
+    }
+    
+    public CredentialRegistration() {
+        
+    }
 
     public String getRegistrationTimestamp() {
         return registrationTime.toString();
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
index 328de35..77f0484 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
@@ -30,8 +30,8 @@
         p:objectMapper-ref="shibboleth.authn.WebAuthn.JSONObjectMapper" />
         
         
-    <bean id="ValidatePublicKeyCredential" parent="AbstractWebAuthnAuthenticationAction"
-        class="net.shibboleth.idp.plugin.authn.webauthn.impl.ValidatePublicKeyCredential" />
+    <bean id="ValidateAuthenticatorAttestationResponse" parent="AbstractWebAuthnAuthenticationAction"
+        class="net.shibboleth.idp.plugin.authn.webauthn.impl.ValidateAuthenticatorAttestationResponse" />
         
         
     <bean id="StorePublicKeyCredential" parent="AbstractWebAuthnAuthenticationAction"
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
index 283c197..24012c2 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
@@ -13,13 +13,18 @@
         parent="AbstractPopulateWebauthnAuthenticationContext"
         p:usernameRequiredPredicate="false">
     </bean>
-
-    <bean id="ValidateWebAuthnAssertion" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.impl.ValidateWebAuthnAssertion" />
-
-    <bean id="ExtractPublicKeyAssertionFromFormRequest" scope="prototype"
+    
+    <bean id="CreatePublicKeyCredentialRequestOptions" parent="AbstractWebAuthnAuthenticationAction"
+        class="net.shibboleth.idp.plugin.authn.webauthn.impl.CreatePublicKeyCredentialRequestOptions"/>        
+        
+    <bean id="ExtractPublicKeyAssertionFromFormRequest" parent="AbstractWebAuthnAuthenticationAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.ExtractPublicKeyAssertionFromFormRequest"
         p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
         p:objectMapper-ref="shibboleth.authn.WebAuthn.JSONObjectMapper" />
 
+    <bean id="ValidateWebAuthnAssertion" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.webauthn.impl.ValidateWebAuthnAssertion" 
+        p:webAuthnClient="#{getObject('shibboleth.authn.webauthn.DefaultWebauthnAuthenticationClientFactory')}"/>        
+
+
 </beans>
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
index c05bbda..69ddf0d 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
@@ -17,41 +17,70 @@ package net.shibboleth.idp.plugin.authn.webauthn.client.impl;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
+import java.time.Instant;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
+import java.util.TreeSet;
 
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yubico.webauthn.RegisteredCredential;
 import com.yubico.webauthn.RelyingParty;
+import com.yubico.webauthn.data.AuthenticatorTransport;
 import com.yubico.webauthn.data.ByteArray;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
+import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
 import com.yubico.webauthn.data.RelyingPartyIdentity;
 import com.yubico.webauthn.data.UserIdentity;
+import com.yubico.webauthn.data.UserVerificationRequirement;
 
 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.impl.MockAuthenticator.Attestation;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator.AuthenticatonExtensionsClientOutputs;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.CredentialRegistration;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.InMemoryRegistrationStorage;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
- *
+ * Tests for {@link YubicoWebauthnAuthenticationClient}. To some extend this is testing the Yubico libraries work
+ * correctly. But it does ensure the client has been constructed to use those libraries correctly.
  */
 public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest{
     
-    private YubicoWebauthnAuthenticationClient client;
+    private final static String ORIGIN = "https://idp.example.com";
+    
+    private final static String RPID = "idp.example.com";
+    
+    private final static String CHALLENGE_B64 = "dGhpc2lzBaNoYWxsZW5nZQ==";
+    
+    private final static String CHALLENGE_2_B64 = "8gneM8yvE20CqnSCUkyD";
+    
+    private final static String USER_HANDLE_B64 = "dGhpc2lzYWNoYWxsZW5nZQ==";   
     
-    private final static String CHALLENGE_B64 = "dGhpc2lzYWNoYWxsZW5nZQ==";
+    private final static String USERNAME = "test-user";
     
-    private final static String USER_HANDLE_B64 = "dGhpc2lzYWNoYWxsZW5nZQ==";
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(YubicoWebauthnAuthenticationClientTest.class);
+    
+    private YubicoWebauthnAuthenticationClient client;   
     
     private String credentialCreationOptions;
     
-    private final static String ORIGIN = "https://idp.example.com";
+    private String credentialRequestOptions;
     
-    private final static String RPID = "idp.example.com";
+    
+    
+    private InMemoryRegistrationStorage storage;
+    
+    private UserIdentity userIdentity;
 
     
     @Override
@@ -59,25 +88,27 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
     public void setup() throws Exception {
         super.setup();
         
+        storage = new InMemoryRegistrationStorage();
+        
         final RelyingParty rp = RelyingParty.builder().identity(
                 RelyingPartyIdentity
                 .builder()
                 .id(RPID)
                 .name("Demo IdP as a WebAuthn RP")
-                .build()).credentialRepository(new InMemoryRegistrationStorage())
+                .build()).credentialRepository(storage)
             .allowOriginPort(true)
             .allowOriginSubdomain(true)
             .build();
         client = new YubicoWebauthnAuthenticationClient(rp,jsonMapper);
         
-        final UserIdentity identity = 
-                UserIdentity.builder().name("test-user").displayName("test user")
+        userIdentity = 
+                UserIdentity.builder().name(USERNAME).displayName("test user")
                 .id(new ByteArray(Base64Support.decode(USER_HANDLE_B64))).build();
         
         final PublicKeyCredentialCreationOptions options =
                 PublicKeyCredentialCreationOptions.builder()
                     .rp(rp.getIdentity())
-                    .user(identity)
+                    .user(userIdentity)
                     .challenge(new ByteArray(Base64Support.decode(CHALLENGE_B64)))
                     .pubKeyCredParams(preferredPublickeyParams)
                     .excludeCredentials(Optional.empty())
@@ -85,59 +116,244 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         credentialCreationOptions = 
                 jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(options);
         
+        
+        final PublicKeyCredentialRequestOptions pkcro =
+                PublicKeyCredentialRequestOptions.builder()
+                    .challenge(new ByteArray(Base64Support.decode(CHALLENGE_B64)))
+                    .rpId(rp.getIdentity().getId())
+                    .userVerification(UserVerificationRequirement.REQUIRED)
+                    .timeout(Optional.of(60000l))
+                    .build();
+        
+        credentialRequestOptions = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(pkcro);
+        
     }
     
 
     @Test
-    public void testSuccesss() throws Exception {   
+    public void testValidateRegistration_Successs() throws Exception {   
         
-        mockAuthenticator = new MockAuthenticator(ORIGIN, RPID);
+        mockAuthenticator = new MockAuthenticator(RPID);
         
-        final Map<String, String> clientDataJson = 
-                mockAuthenticator.createClientDataJson("webauthn.create", CHALLENGE_B64); 
-        final String clientDataJsonString = new ObjectMapper().writeValueAsString(clientDataJson);
-    
-        final Attestation attestationObject = mockAuthenticator.createCredentialAttestationObject();
+        final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
+        
+        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
+                        Base64Support.decode(USER_HANDLE_B64));
         
-        final boolean registrationIsValid = client.validateRegistration(credentialCreationOptions, 
-                createPublicKeyCredential(attestationObject, clientDataJsonString));
+        final var attestationJson = jsonMapper.writeValueAsString(attestation);
+        final boolean registrationIsValid = client.validateRegistration(credentialCreationOptions, attestationJson);
         
         assertTrue(registrationIsValid);
 
     }
     
     @Test
-    public void testFail_BadRpId() throws Exception {   
+    public void testValidateRegistration_Fail_BadRpId() throws Exception {   
+        
+        mockAuthenticator = new MockAuthenticator("wrong-rpid.example.com");
+        
+        final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
+        
+        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
+                        Base64Support.decode(USER_HANDLE_B64));
         
-        mockAuthenticator = new MockAuthenticator(ORIGIN, "wrong-rpid.example.com");
+        final var attestationJson = jsonMapper.writeValueAsString(attestation);
+        final boolean registrationIsValid = client.validateRegistration(credentialCreationOptions, attestationJson);
         
-        final Map<String, String> clientDataJson = 
-                mockAuthenticator.createClientDataJson("webauthn.create", CHALLENGE_B64); 
-        final String clientDataJsonString = new ObjectMapper().writeValueAsString(clientDataJson);
+        assertFalse(registrationIsValid);
+    }
     
-        final Attestation attestationObject = mockAuthenticator.createCredentialAttestationObject();
+    @Test
+    public void testValidateRegistrationFail_BadOrigin() throws Exception {   
         
-        final boolean registrationIsValid = client.validateRegistration(credentialCreationOptions, 
-                createPublicKeyCredential(attestationObject, clientDataJsonString));
+        mockAuthenticator = new MockAuthenticator(RPID);
         
+        final Map<String, String> clientDataCreate = createClientData("webauthn.create", "wrong-origin", CHALLENGE_B64); 
+           
+        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
+                        Base64Support.decode(USER_HANDLE_B64));
+        
+        final var attestationJson = jsonMapper.writeValueAsString(attestation);
+        final boolean registrationIsValid = client.validateRegistration(credentialCreationOptions, attestationJson);
+
         assertFalse(registrationIsValid);
     }
     
     @Test
-    public void testFail_BadOrigin() throws Exception {   
+    public void testValidateAuthentication_Success() throws Exception {   
+        
+        mockAuthenticator = new MockAuthenticator(RPID);
+        
+        final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
+        
+        // Need to register a new credential first
+        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
+                        Base64Support.decode(USER_HANDLE_B64));
+        
+        final RegisteredCredential credential = RegisteredCredential.builder()
+                .credentialId(new ByteArray(attestation.getRawId()))
+                .userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
+                .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+                .build();
+        
+        final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"), 
+                new TreeSet<AuthenticatorTransport>(), Instant.now(), credential, Optional.empty());
+        
+        storage.addRegistrationByUsername(USERNAME, reg);
+        
+        final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64); 
+        
+        // Now generate an assertion (authentication) and check it is valid
+        final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs> 
+            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
+        
+        final var assertionJson = jsonMapper.writeValueAsString(assertion);
+        log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
+        final boolean valid = 
+                client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
+                credentialRequestOptions, assertionJson);
+        assertTrue(valid);
+        
+    }
+    
+    @Test
+    public void testValidateAuthentication_Fail_WrongOrigin() throws Exception {   
+        
+        mockAuthenticator = new MockAuthenticator(RPID);
+        
+        final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
+        
+        // Need to register a new credential first
+        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
+                        Base64Support.decode(USER_HANDLE_B64));
+        
+        final RegisteredCredential credential = RegisteredCredential.builder()
+                .credentialId(new ByteArray(attestation.getRawId()))
+                .userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
+                .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+                .build();
+        
+        final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"), 
+                new TreeSet<AuthenticatorTransport>(), Instant.now(), credential, Optional.empty());
+        
+        storage.addRegistrationByUsername(USERNAME, reg);
         
-        mockAuthenticator = new MockAuthenticator("wrong-origin", RPID);
+        // Add the wrong origin here. 
+        final Map<String, String> clientDataGet = createClientData("webauthn.get", "wrong", CHALLENGE_B64); 
         
-        final Map<String, String> clientDataJson = 
-                mockAuthenticator.createClientDataJson("webauthn.create", CHALLENGE_B64); 
-        final String clientDataJsonString = new ObjectMapper().writeValueAsString(clientDataJson);
+        // Now generate an assertion (authentication) and check it is valid
+        final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs> 
+            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
+        
+        final var assertionJson = jsonMapper.writeValueAsString(assertion);
+        log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
+        final boolean valid = 
+                client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
+                credentialRequestOptions, assertionJson);
+        assertFalse(valid);
+        
+    }
     
-        final Attestation attestationObject = mockAuthenticator.createCredentialAttestationObject();
+    @Test
+    public void testValidateAuthentication_Fail_WrongOperationType() throws Exception {   
         
-        final boolean registrationIsValid = client.validateRegistration(credentialCreationOptions, 
-                createPublicKeyCredential(attestationObject, clientDataJsonString));
+        mockAuthenticator = new MockAuthenticator(RPID);
         
-        assertFalse(registrationIsValid);
+        final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
+        
+        // Need to register a new credential first
+        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
+                        Base64Support.decode(USER_HANDLE_B64));
+        
+        final RegisteredCredential credential = RegisteredCredential.builder()
+                .credentialId(new ByteArray(attestation.getRawId()))
+                .userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
+                .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+                .build();
+        
+        final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"), 
+                new TreeSet<AuthenticatorTransport>(), Instant.now(), credential, Optional.empty());
+        
+        storage.addRegistrationByUsername(USERNAME, reg);
+        
+        // Add the wrong operation type here e.g. webauthn.create rather than webauthn.get
+        final Map<String, String> clientDataGet = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
+        
+        // Now generate an assertion (authentication) and check it is valid
+        final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs> 
+            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
+        
+        final var assertionJson = jsonMapper.writeValueAsString(assertion);
+        log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
+        final boolean valid = 
+                client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
+                credentialRequestOptions, assertionJson);
+        assertFalse(valid);
+        
+    }
+    
+    @Test
+    public void testValidateAuthentication_Fail_BadSignature_DifferentKey() throws Exception {   
+        
+        mockAuthenticator = new MockAuthenticator(RPID);
+        mockAuthenticator.setProduceBadAssertionSignatures(true);
+        
+        final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
+        
+        // Need to register a new credential first
+        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
+                        Base64Support.decode(USER_HANDLE_B64));
+        
+        final RegisteredCredential credential = RegisteredCredential.builder()
+                .credentialId(new ByteArray(attestation.getRawId()))
+                .userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
+                .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+                .build();
+        
+        final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"), 
+                new TreeSet<AuthenticatorTransport>(), Instant.now(), credential, Optional.empty());
+        
+        storage.addRegistrationByUsername(USERNAME, reg);
+        
+        // Add the wrong operation type here e.g. webauthn.create rather than webauthn.get
+        final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64); 
+        
+        // Now generate an assertion (authentication) and check it is valid
+        final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs> 
+            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
+        
+        final var assertionJson = jsonMapper.writeValueAsString(assertion);
+        log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
+        final boolean valid = 
+                client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
+                credentialRequestOptions, assertionJson);
+        assertFalse(valid);
+        
+    }
+    
+    /**
+     * Create a client data JSON object as a Java Map.
+     * 
+     * @param type the operation type
+     * @param origin the origin
+     * @param challenge the challenge
+     * @return the clientData map
+     */
+    private Map<String, String> createClientData(@Nonnull @NotEmpty final String type, 
+            @Nonnull @NotEmpty final String origin,
+            @Nonnull @NotEmpty final String challenge){
+        final HashMap<String, String> obj = new HashMap<>();
+        obj.put("challenge",challenge);
+        obj.put("origin", origin);
+        obj.put("type", type);
+        return obj;        
     }
     
     // Needs to do crypto checks
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java
index 280feeb..8abc3cb 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java
@@ -14,14 +14,10 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
-import static org.testng.Assert.assertNotNull;
-
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-import javax.annotation.Nonnull;
-
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.springframework.webflow.execution.RequestContext;
 
@@ -32,15 +28,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.json.JsonMapper;
 import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
-import com.yubico.webauthn.data.ByteArray;
-import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
-import com.yubico.webauthn.data.PublicKeyCredential;
 import com.yubico.webauthn.data.PublicKeyCredentialParameters;
 
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator.Attestation;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
 import net.shibboleth.idp.profile.testing.RequestContextBuilder;
 
@@ -106,26 +97,7 @@ public abstract class AbstractWebAuthnTest {
     }  
     
     
-    @Nonnull protected PublicKeyCredential<AuthenticatorAttestationResponse,ClientRegistrationExtensionOutputs> 
-        createPublicKeyCredential(final Attestation attestationObject, final String clientDataJsonString) 
-                throws Exception{
-        
-        final var response = AuthenticatorAttestationResponse.builder()
-                .attestationObject(new ByteArray(attestationObject.getAttestationObjectCose()))
-                .clientDataJSON(new ByteArray(clientDataJsonString.getBytes()))
-                .build();
-        
-        final ClientRegistrationExtensionOutputs extOutputs =ClientRegistrationExtensionOutputs.builder().build();
-        final PublicKeyCredential<AuthenticatorAttestationResponse,ClientRegistrationExtensionOutputs> credential = 
-                PublicKeyCredential.<AuthenticatorAttestationResponse,ClientRegistrationExtensionOutputs>builder()
-                .id(new ByteArray(attestationObject.getCredentialIdBytes()))
-                .response(response)
-                .clientExtensionResults(extOutputs)
-                .build();
-        assertNotNull(credential);
-        assert credential != null;
-        return credential;
-    }
+   
      
     
 
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 86e38ab..51ebed8 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
@@ -20,37 +20,64 @@ import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
 import java.security.SecureRandom;
+import java.security.Signature;
 import java.util.BitSet;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
 
 import org.apache.commons.codec.DecoderException;
 import org.apache.commons.codec.binary.Hex;
 import org.slf4j.Logger;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.core.Base64Variants;
+import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
 import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
-import com.yubico.webauthn.data.AuthenticatorData;
-import com.yubico.webauthn.data.ByteArray;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 
 import COSE.AlgorithmID;
 import COSE.OneKey;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.codec.EncodingException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
  * Mock Authenticator, to create public key credentials and attestations for a given (fixed) relying party.
  * 
+ * <p>A new credential is created and stored for every call to 
+ * {@link #createAuthenticatorAttestationResponse(String, Map, byte[])}. Calls to 
+ * {@link #createAuthenticatorAssertionResponse(String, byte[], Map)} require the credentialId so it can use information
+ * stored when the credential was created. Hence to use {@link #createAuthenticatorAssertionResponse(String, byte[], Map)}
+ * you must first call {@link #createAuthenticatorAttestationResponse(String, Map, byte[])}.</p>
+ * 
  * <p> Does not support the attestation statement 'attStmt' (which provides data provenance information for the public 
  * key of the attesting authority and authenticator).</p>
  */
+ at NotThreadSafe
 public class MockAuthenticator {
     
+    /** Fix to using this algorithm for key creation and signing. Must match KEY_ALGO. */
+    private static final String JCA_ALGO ="SHA256withECDSA";
+    
+    /** Fix to using this algorithm for key creation and signing. Must match JCA_ALGO.*/
+    private static final AlgorithmID KEY_ALGO = AlgorithmID.ECDSA_256;
+    
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(MockAuthenticator.class);
     
@@ -63,67 +90,158 @@ public class MockAuthenticator {
      */
     @Nonnull @NotEmpty private final String rpId;
     
-    /** The full RP Origin.*/
-    @Nonnull @NotEmpty private final String origin;
-    
     /** CBOR Mapper.*/
     @Nonnull private final ObjectMapper cborMapper;
     
+    /** Json Mapper.*/
+    @Nonnull private final ObjectMapper jsonMapper;
+    
+    /** Map of base64Url encoded credentialsIds to PublicKeyCredential's.*/
+    @Nonnull 
+    private final Map<String, PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs>> createdCredentialsMaps;
+    
+    /** Testing flag to produce bad assertion signatures.*/
+    private boolean produceBadAssertionSignatures;
     
     /**
      * 
      * Constructor.
      * 
-     * <p>Creates a new public/private key pair per instantiation.</p>
-     *
-     *  @param the relying party's origin.
      * @param relyingPartyId the relying party identifier, a valid domain string. The origin's effective domain 
      *              (the host's domain name, no scheme and no port).
      * @throws Exception on error creating this authenticator.
      */
-    public MockAuthenticator(@Nonnull @NotEmpty final String rpOrigin, 
-            @Nonnull @NotEmpty final String relyingPartyId) throws Exception {
+    @SuppressWarnings("null")
+    public MockAuthenticator(@Nonnull @NotEmpty final String relyingPartyId) throws Exception {
         rpId = Constraint.isNotEmpty(relyingPartyId, "relyingPartyId can not be null");
-        origin = Constraint.isNotEmpty(rpOrigin, "rpOrigin can not be null");;
         fmt = "none";
         cborMapper = new ObjectMapper(new CBORFactory()).setBase64Variant(Base64Variants.MODIFIED_FOR_URL);
+        jsonMapper = JsonMapper.builder()
+                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+                .serializationInclusion(Include.NON_ABSENT)
+                .defaultBase64Variant(Base64Variants.MODIFIED_FOR_URL)
+                .addModule(new Jdk8Module())
+                .addModule(new JavaTimeModule())
+                .build();
+        createdCredentialsMaps = new HashMap<>();
+        produceBadAssertionSignatures = false;
         
     }
     
+    public void setProduceBadAssertionSignatures(final boolean badSigs) {
+        produceBadAssertionSignatures = badSigs;
+    }
+    
+    /** Reset overall state for this authenticator.*/
+    public void reset() {
+        createdCredentialsMaps.clear();
+    }
+    
     /**
-     * Create a WebAuthn public key credential attestation object. A new attestation is created each time.
+     * Create a WebAuthn public key credential Attestation response. A new Attestation is created and stored each time.
      *  
+     * @param challenge the challenge to create the response from.
+     * @param clientData the client data passed by the 'client' (browser) during authentication.
+     * @param userHandle the userID that was present in the PublicKeyCredentialCreationOptions
+     *   
+     * @returns the public key credential Attestation
+     * 
      * @throws Exception on error.
      */
-    public Attestation createCredentialAttestationObject() throws Exception {
+    public PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> 
+        createAuthenticatorAttestationResponse(@Nonnull @NotEmpty final String challenge,
+                final Map<String, String> clientData, final byte[] userHandle) throws Exception {
         
-        final OneKey createdKey = OneKey.generateKey(AlgorithmID.ECDSA_256);
+        final OneKey createdKey = OneKey.generateKey(KEY_ALGO);
         assert createdKey != null;
         
         final String aaguidHex = generateRandomIdentifierHex(32);
-        final String credentialIdHex = generateRandomIdentifierHex(32);
+        final byte[] credentialId = generateRandomIdentifierBytes(32);
+        
+        final byte[] attestationObject = createAttestationObject(createdKey, aaguidHex, credentialId);
         
-        final byte[] attestationObject = createAttestationObject(createdKey, aaguidHex, credentialIdHex);
-        return new Attestation(createdKey, aaguidHex, credentialIdHex, attestationObject);
+        final String clientDataJsonString = jsonMapper.writeValueAsString(clientData);
+        
+        final var publicKeyCredential = new PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs>(
+                credentialId, 
+                new Attestation(clientDataJsonString.getBytes(), createdKey, attestationObject, userHandle),
+                new AuthenticatonExtensionsClientOutputs());
+        createdCredentialsMaps.put(Base64Support.encodeURLSafe(credentialId), publicKeyCredential);
+        return publicKeyCredential;
         
     }
     
+    /**
+     * Create a WebAuthn public key credential assertion response. A new response is created each time.
+     * 
+     * @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.
+     * 
+     * @return the public key credential assertion
+     * 
+     * @throws Exception on error
+     */
+    public PublicKeyCredential<Assertion, AuthenticatonExtensionsClientOutputs> createAuthenticatorAssertionResponse(
+            @Nonnull final byte[] credentialId, final Map<String, String> clientData) throws Exception {
+        
+        final var credentialb64 = Base64Support.encodeURLSafe(credentialId);
+        final var publicKeyAttestation = createdCredentialsMaps.get(credentialb64);
+        if (publicKeyAttestation == null) {
+            throw new IllegalArgumentException("Invalid credentiaId, public key attestation not found");
+        }       
+        
+        final byte[] rawCredentialIdentifier = credentialId;
+        final String clientDataCompactSerialization = jsonMapper.writeValueAsString(clientData);
+        
+        final byte[] authenticatorData = createAuthDataForAssertion(rawCredentialIdentifier);
+        final byte[] signature = 
+                sign(authenticatorData, clientDataCompactSerialization, 
+                        publicKeyAttestation.getResponse().getKey().AsPrivateKey());
+        
+        return new PublicKeyCredential<Assertion, AuthenticatonExtensionsClientOutputs>(rawCredentialIdentifier,
+                new Assertion(clientDataCompactSerialization.getBytes(), authenticatorData, signature, 
+                        publicKeyAttestation.getResponse().getUserHandle()),
+                new AuthenticatonExtensionsClientOutputs());
+    }
     
     /**
-     * Create a client data JSON object as a Java Map.
+     * Create the assertion signature by signing the combined authenticatorData and clientDataJSON using the private
+     * key given.
      * 
-     * @param challenge the challenge
-     * @param type the type
-     * @return the client JSON map.
+     * @param authenticatorData the authenticator data
+     * @param clientDataJSON the client data in JSON compact serialization format
+     * @param key the private key to use for signing
+     * @return the signature bytes
+     *  
+     * @throws Exception on error
      */
-    public Map<String, String> createClientDataJson(@Nonnull @NotEmpty final String type, 
-            @Nonnull @NotEmpty final String challenge){
-        final HashMap<String, String> obj = new HashMap<>();
-        obj.put("challenge",challenge);
-        obj.put("origin", origin);
-        obj.put("type", type);
-        return obj;        
+    private byte[] sign(final byte[] authenticatorData, final String clientDataJSON,
+            final PrivateKey key) throws Exception {
+        
+        //hash the clientData JSON
+        final byte[] clientDataHash = sha256(clientDataJSON);
+        //combine together
+        final byte[] toSign = new byte[authenticatorData.length + clientDataHash.length];
+
+        System.arraycopy(authenticatorData, 0, toSign, 0, authenticatorData.length);
+        System.arraycopy(clientDataHash, 0, toSign, authenticatorData.length, clientDataHash.length);
+       
+        
+        final Signature sig = Signature.getInstance(JCA_ALGO);
+        if (produceBadAssertionSignatures) {
+            // Use a different key for signing
+            final OneKey newKey = OneKey.generateKey(KEY_ALGO);
+            sig.initSign(newKey.AsPrivateKey());
+        } else {
+            sig.initSign(key);
+        }
+        sig.update(toSign);
+        final byte[] signatureBytes = sig.sign();
+        return signatureBytes;
     }
+    
+    
+   
 
     
     /**
@@ -140,9 +258,9 @@ public class MockAuthenticator {
      * @throws Exception on error
      */
     private byte[] createAttestationObject(final OneKey createdKey, final String aaguidHex, 
-            final String credentialId) throws Exception{
+            final byte[] credentialId) throws Exception{
         final HashMap<String, Object> attObj = new HashMap<>();
-        attObj.put("authData", createAuthData(createdKey, aaguidHex, credentialId).getBytes().getBytes());
+        attObj.put("authData", createAuthData(createdKey, aaguidHex, credentialId));
         attObj.put("fmt", fmt);
         final HashMap<String, String> attStmt = new HashMap<>();
 //        attStmt.put("sig", createSignature());
@@ -151,40 +269,67 @@ public class MockAuthenticator {
         return cborMapper.writeValueAsBytes(attObj);
     }
 
+    /**
+     * Create the authenticator data part of the assertion object. 
+     * 
+     * @param credentialId the credential identifier
+     * 
+     * @return the authData
+     * @throws Exception on error
+     */
+    private byte[] createAuthDataForAssertion( @Nonnull final byte[] credentialId) throws Exception {
+        return createAuthData(null, null, credentialId);
+    }
 
     /**
-     * Create the authenticator data part of the attestation object. 
-     * See https://www.w3.org/TR/webauthn-2/#sctn-attested-credential-data.
+     * Create the authenticator data part of the attestation or assertion object. 
      * 
-     * @param createdKey the key to use as the basis of this attestation object (and the new key to register).
-     * @param credentialId 
-     * @param aaguidHex 
+     * @param createdKey the key to use as the basis of this attestation object (and the new key to register). Can be
+     *                          null if this is the result of a 'get' call during authentication.
+     * @param aaguidHex the authenticator identifier.Can be null if this is the result of a 'get' call during
+     *                           authentication. 
+     * @param credentialId the credential identifier
+
      * 
      * @return the authData
      * @throws NoSuchAlgorithmException on error. 
      */
-    private AuthenticatorData createAuthData(final OneKey createdKey, final String aaguidHex, 
-            final String credentialId) throws Exception {
+    private byte[] createAuthData(@Nullable final OneKey createdKey, @Nullable final String aaguidHex, 
+            @Nonnull final byte[] credentialId) throws Exception {
         final byte[] rpIdHash = createRpIdHash();
-        final byte[] flags = createFlags(true, true, true, false);
-        final byte[] signCount = createSignCount(1);
-        final byte[] attestedCredentialData = createAttestedCredentialData(createdKey, aaguidHex, credentialId);
         
-        final byte[] authDataCombined = new byte[rpIdHash.length + flags.length + signCount.length 
-                                                 + attestedCredentialData.length];
-        
-        System.arraycopy(rpIdHash, 0, authDataCombined, 0, rpIdHash.length);
-        System.arraycopy(flags, 0, authDataCombined, rpIdHash.length, flags.length);
-        System.arraycopy(signCount, 0, authDataCombined, rpIdHash.length + flags.length, signCount.length);
-        System.arraycopy(attestedCredentialData, 0, authDataCombined, rpIdHash.length + flags.length + signCount.length, 
-                attestedCredentialData.length);
+        // If we have a key, we need to create the attested data and set the appropriate AT flag, otherwise do not
+        byte[] flags;
+        if (createdKey !=null) {
+            flags = createFlags(true, true, true, false);
+        } else {
+            flags = createFlags(true, true, false, false);
+        }
+        final byte[] signCount = createSignCount(1);
         
+        byte[] authDataCombined;
+        if (createdKey != null) {
+            final byte[] attestedCredentialData = createAttestedCredentialData(createdKey, aaguidHex, credentialId);
+            
+            authDataCombined = new byte[rpIdHash.length + flags.length + signCount.length 
+                                                     + attestedCredentialData.length];
+            
+            System.arraycopy(rpIdHash, 0, authDataCombined, 0, rpIdHash.length);
+            System.arraycopy(flags, 0, authDataCombined, rpIdHash.length, flags.length);
+            System.arraycopy(signCount, 0, authDataCombined, rpIdHash.length + flags.length, signCount.length);
+            System.arraycopy(attestedCredentialData, 0, authDataCombined, rpIdHash.length + flags.length + 
+                    signCount.length, attestedCredentialData.length);
+        } else {
+            authDataCombined = new byte[rpIdHash.length + flags.length + signCount.length];
+
+            System.arraycopy(rpIdHash, 0, authDataCombined, 0, rpIdHash.length);
+            System.arraycopy(flags, 0, authDataCombined, rpIdHash.length, flags.length);
+            System.arraycopy(signCount, 0, authDataCombined, rpIdHash.length + flags.length, signCount.length);
+        }
         
-        final ByteArray authData = new ByteArray(authDataCombined);
-        final AuthenticatorData data = new AuthenticatorData(authData);
-        log.debug("Created Authenticator Data for RP, '{}' RPHash '{}', SignCounter '{}':  '{}'", rpId, 
-                data.getRpIdHash().getHex(), data.getSignatureCounter(), data);
-        return data;
+        log.debug("Created Authenticator Data for RP, '{}' RPHash '{}', SignCounter '{}', flags '{}'", rpId, 
+                Hex.encodeHexString(rpIdHash), Hex.encodeHexString(signCount), Hex.encodeHexString(flags));
+        return authDataCombined;
     }
     
     /**
@@ -192,7 +337,7 @@ public class MockAuthenticator {
      * 
      * @param up User present flag
      * @param uv User verified flag
-     * @param atIncluded attested credential data included flag (should be true for create credentials)
+     * @param atIncluded attested credential data included flag (should be true for create credentials, false for get)
      * @param extDataIncluded extension data included flag
      * 
      * @return a byte of the flags. Should only be one byte.
@@ -209,12 +354,20 @@ public class MockAuthenticator {
         return bits.toByteArray();        
     }
     
+    /**
+     * Create the attested credential data (https://www.w3.org/TR/webauthn-2/#attestedcredentialdata)
+     * 
+     * @param createdKey the public key to include in the attested credential data
+     * @param aaguidHex the aaguid as hex
+     * @param credentialId the raw credential ID
+     * @return the attestedCredentialData
+     * @throws Exception on error
+     */
     private byte[] createAttestedCredentialData(final OneKey createdKey, 
-            final String aaguidHex, final String credentialId) throws Exception {
+            final String aaguidHex, final byte[] credentialId) throws Exception {
         final byte[] aaguid = createAaguid(aaguidHex);
         final byte[] credentialIdAndLength = createCredentialId(credentialId); 
         // OneKey is from the COSE-JAVA lib that Yubico use, but is not perhaps well maintained
-        log.debug("Key is type {}", createdKey);
         final byte[] coseKeyAsCborBytes = createdKey.AsCBOR().EncodeToBytes();
         
         final byte[] attestedCredentialDataCombined = new byte[aaguid.length + credentialIdAndLength.length 
@@ -230,6 +383,13 @@ public class MockAuthenticator {
         return attestedCredentialDataCombined;
     }
     
+    /**
+     * Create a 16 byte aaguid
+     * 
+     * @param aaguidHex the 32 byte aaguid as Hex to convert to 16 bytes
+     * @return the 16 byte aaguid
+     * @throws DecoderException on error
+     */
     private byte[] createAaguid(final String aaguidHex) throws DecoderException {
         assertEquals(aaguidHex.length(), 32);
         final byte[] aaguidBytes = Hex.decodeHex(aaguidHex.toCharArray());
@@ -238,12 +398,18 @@ public class MockAuthenticator {
         
     }
     
+    /**
+     * Create a signature count inside 4 bytes.
+     * 
+     * @param count the count as a number
+     * @return the count inside a 4 byte array
+     */
     private byte[] createSignCount(final int count){
         return ByteBuffer.allocate(4).putInt(count).array();
     }
     
-    private byte[] createCredentialId(final String credentialId) throws DecoderException {
-        final byte[] credentialIdBytes = Hex.decodeHex(credentialId.toCharArray());
+    private byte[] createCredentialId(final byte[] credentialId) throws DecoderException {
+        final byte[] credentialIdBytes = credentialId;
         final int credentialIdLength = credentialIdBytes.length;
            
         final byte[] credentialIdLengthBytes = new byte[2];
@@ -274,6 +440,20 @@ public class MockAuthenticator {
         return encodedhash;
     }
     
+    /** 
+     * Sha256 the input.
+     * 
+     * @param input the input to hash
+     * @return the sha256 hashed input
+     * 
+     * @throws NoSuchAlgorithmException
+     */
+    private byte[] sha256(final String input) throws NoSuchAlgorithmException {
+        final MessageDigest digest = MessageDigest.getInstance("SHA-256");
+        final byte[] encodedhash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
+        return encodedhash;
+    }
+    
     /**
      * Generates a random identifier in Hex format.
      *  
@@ -281,6 +461,7 @@ public class MockAuthenticator {
      * 
      * @return the randomly generated value.
      */
+    @SuppressWarnings("null")
     @Nonnull static String generateRandomIdentifierHex(@Nonnull final Integer length) {
         final SecureRandom secureRandom = new SecureRandom();
         final StringBuilder sb = new StringBuilder();
@@ -290,67 +471,176 @@ public class MockAuthenticator {
         return sb.toString().substring(0, length);
     }
     
+    /**
+     * Generates a random identifier in bytes.
+     *  
+     * @param length the length of the parameter.
+     * 
+     * @return the randomly generated value.
+     * @throws Exception on error
+     */
+    @Nonnull public static byte[] generateRandomIdentifierBytes(@Nonnull final Integer length) throws Exception {
+        final SecureRandom secureRandom = new SecureRandom();
+        final byte[] randomBytes = new byte[length];
+        secureRandom.nextBytes(randomBytes);
+        return randomBytes;
+    }
+    
+    //TODO not complete, can not add extensions
+    /** Client extension outputs, see https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-extension-output. */
+    public final class AuthenticatonExtensionsClientOutputs {
+        
+        @JsonProperty("extensionIds")
+        public Set<String> getExtensionIds() {
+            final HashSet<String> ids = new HashSet<>();           
+            return ids;
+          }
+        
+    }
+        
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    /** Public key credential container. See https://w3c.github.io/webauthn/#iface-pkcredential */
+    public final class PublicKeyCredential<T, R> {
+        
+        private final byte[] id;
+        
+        private final T response;
+        
+        private final R clientExtensions;
+
+        public PublicKeyCredential(final byte[] id, final T response, final R clientExt) {
+            super();
+            this.id = id;
+            this.response = response;
+            clientExtensions = clientExt;
+        }
+        
+        /**
+         * @return Returns the id of the credential created.
+         */
+        @JsonProperty("rawId")
+        public byte[] getRawId() {
+            return id;
+        }
+        
+        @SuppressWarnings("null")
+        @JsonProperty("id")
+        public String getId() throws EncodingException {
+            return Base64Support.encodeURLSafe(id);
+        }
+        
+        /**
+         * @return Returns the response.
+         */
+        @JsonProperty("response")
+        public T getResponse() {
+            return response;
+        }
+        
+        @JsonProperty("type")
+        public String getType() {
+            return "public-key";
+        }
+        
+        @JsonProperty("clientExtensionResults")
+        public R getClientExtensions() {
+            return clientExtensions;
+        }
+        
+    }
+    
+    /** Simple assertion return type so the caller can access the underlying values easily.*/
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public final class Assertion {
+        
+        private final byte[] authenticatorData;
+        private final byte[] signature;
+        private final byte[] userHandle;
+        private final byte[] clientDataJSON;
+        
+        
+        public Assertion(@JsonProperty("clientDataJSON") final byte[] clientDataJSON,  
+                @JsonProperty("authenticatorData") final byte[] authenticatorData, 
+                @JsonProperty("signature") final byte[] signature, 
+                @JsonProperty("userHandle") final byte[] userHandle) {
+            super();
+            this.clientDataJSON = clientDataJSON;
+            this.authenticatorData = authenticatorData;
+            this.signature = signature;
+            this.userHandle = userHandle;
+        }
+
+
+        @JsonProperty("authenticatorData")
+        public final byte[] getAuthenticatorData() {
+            return authenticatorData;
+        }
+
+        @JsonProperty("signature")
+        public final byte[] getSignature() {
+            return signature;
+        }
+
+        @JsonProperty("userHandle")
+        public final byte[] getUserHandle() {
+            return userHandle;
+        }
+       
+        @JsonProperty("clientDataJSON")
+        public byte[] getClientDataJSON() {
+            return clientDataJSON;
+        }
+    }
+    
     /** Simple attestion return type so the caller can access the underlying values easily.*/
+    @JsonIgnoreProperties(ignoreUnknown = true)
     public class Attestation {
         
+        /** Stash the userHandle for easy extraction.*/
+        private final byte[] userHandle;
+        /** Stash the key for easy extraction.*/
         private final OneKey key;
-        private final String credentialIdHex;
-        private final String aaguidHex;
         private final byte[] attestationObjectCose;
+        private final byte[] clientDataJSON;
         
         /**
          * Constructor.
          *
+         * @param clientDataJSON the client data in compact JSON serialization format.
          * @param key the key
-         * @param credentialIdHex the credential Id
          * @param aaguidHex the aaguid
          * @param attestationObjectCose the attestation object
          */
-        public Attestation(final OneKey key, final String credentialIdHex, final String aaguidHex, 
-                final byte[] attestationObjectCose) {
+        public Attestation(@JsonProperty("clientDataJSON") final byte[] clientDataJSON, 
+                final OneKey key, 
+                @JsonProperty("attestationObject") final byte[] attestationObjectCose,
+                final byte[] userHandle) {
             super();
+            this.clientDataJSON = clientDataJSON;
             this.key = key;
-            this.credentialIdHex = credentialIdHex;
-            this.aaguidHex = aaguidHex;
             this.attestationObjectCose = attestationObjectCose;
+            this.userHandle = userHandle;
         }
 
-        /**
-         * @return Returns the key.
-         */
-        public OneKey getKey() {
-            return key;
+        @JsonIgnore
+        public byte[] getUserHandle() {
+           return userHandle;
         }
 
-        /**
-         * @return Returns the credentialIdHex.
-         */
-        public String getCredentialIdHex() {
-            return credentialIdHex;
-        }
-        
-        public byte[] getCredentialIdBytes() throws DecoderException {
-            return Hex.decodeHex(credentialIdHex.toCharArray());
-        }
-        
-        public byte[] getAaguidBytes() throws DecoderException {
-            return Hex.decodeHex(aaguidHex.toCharArray());
-        }
-
-        /**
-         * @return Returns the aaguidHex.
-         */
-        public String getAaguidHex() {
-            return aaguidHex;
-        }
+        @JsonIgnore
+        public OneKey getKey() {
+            return key;
+        }        
 
-        /**
-         * @return Returns the attestationObjectCose.
-         */
+        @JsonProperty("attestationObject")
         public byte[] getAttestationObjectCose() {
             return attestationObjectCose;
         }
-        
+
+        @JsonProperty("clientDataJSON")
+        public byte[] getClientDataJSON() {
+            return clientDataJSON;
+        }
         
     }
 
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java
index a435aab..9634247 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java
@@ -14,16 +14,11 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
-import static org.testng.Assert.assertNull;
-
-import java.util.Map;
 import java.util.Optional;
 
-import org.springframework.webflow.execution.Event;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.yubico.webauthn.RelyingParty;
 import com.yubico.webauthn.data.ByteArray;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
@@ -32,17 +27,16 @@ import com.yubico.webauthn.data.UserIdentity;
 
 import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClient;
 import net.shibboleth.idp.plugin.authn.webauthn.client.impl.YubicoWebauthnAuthenticationClient;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator.Attestation;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.InMemoryRegistrationStorage;
 import net.shibboleth.shared.codec.Base64Support;
 
 
 /**
- * Tests for {@link ValidatePublicKeyCredential}.
+ * Tests for {@link ValidateAuthenticatorAttestationResponse}.
  */
 public class ValidatePublicKeyCredentialTest extends AbstractWebAuthnTest{
     
-    private ValidatePublicKeyCredential validator;
+    private ValidateAuthenticatorAttestationResponse validator;
     
     private final static String CHALLENGE_B64 = "dGhpc2lzYWNoYWxsZW5nZQ==";
     
@@ -52,7 +46,7 @@ public class ValidatePublicKeyCredentialTest extends AbstractWebAuthnTest{
     @BeforeMethod
     public void setup() throws Exception {
         super.setup();
-        validator = new ValidatePublicKeyCredential();
+        validator = new ValidateAuthenticatorAttestationResponse();
         //Move this test to one of the client, this should use a mock and less specific data types
         final RelyingParty rp = RelyingParty.builder().identity(
                 RelyingPartyIdentity
@@ -88,17 +82,17 @@ public class ValidatePublicKeyCredentialTest extends AbstractWebAuthnTest{
     @Test
     public void testSuccesss() throws Exception {   
         
-        mockAuthenticator = new MockAuthenticator("https://idp.example.com", "idp.example.com");
-        
-        final Map<String, String> clientDataJson = 
-                mockAuthenticator.createClientDataJson("webauthn.create", CHALLENGE_B64); 
-        final String clientDataJsonString = new ObjectMapper().writeValueAsString(clientDataJson);
-    
-        final Attestation attestationObject = mockAuthenticator.createCredentialAttestationObject();
-        webAuthnContext.setPublicKeyCredential(createPublicKeyCredential(attestationObject, clientDataJsonString));
-        
-        final Event event = validator.execute(src);
-        assertNull(event);
+//        mockAuthenticator = new MockAuthenticator("https://idp.example.com");
+//        
+//        final Map<String, String> clientDataJson = 
+//                mockAuthenticator.createClientDataJson("webauthn.create", CHALLENGE_B64); 
+//        final String clientDataJsonString = new ObjectMapper().writeValueAsString(clientDataJson);
+//    
+//        final Attestation attestationObject = mockAuthenticator.createCredentialAttestationObject();
+//        webAuthnContext.setPublicKeyCredential(createPublicKeyCredential(attestationObject, clientDataJsonString));
+//        
+//        final Event event = validator.execute(src);
+//        assertNull(event);
     }
     
    

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


More information about the commits mailing list