[java-idp-plugin-webauthn] branch main updated: JWEBAUTHN-26 - Allow metadata to be attached to registrations retroactively

Phil Smart philip.smart at jisc.ac.uk
Wed Sep 11 14:59:22 UTC 2024


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-idp-plugin-webauthn.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-webauthn.git;a=commit;h=69e921e617cdb474c43d8d2fdb7b02ab437819c8

The following commit(s) were added to refs/heads/main by this push:
     new 69e921e  JWEBAUTHN-26 - Allow metadata to be attached to registrations retroactively
69e921e is described below

commit 69e921e617cdb474c43d8d2fdb7b02ab437819c8
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Sep 11 15:59:20 2024 +0100

    JWEBAUTHN-26 - Allow metadata to be attached to registrations
    retroactively
    
     - Remove the metadata entry from the storage record
     - Add an enhanced credential registration wrapper, for storing the
    credential and authenticator metadata
     - Add metadata lookup (if the metadata service is configured) during
    the credential lookup action of the authn, registration, and management
    flows
    
    https://shibboleth.atlassian.net/browse/JWEBAUTHN-26
---
 .../webauthn/context/BaseWebAuthnContext.java      |  10 +-
 .../context/WebAuthnManagementContext.java         |  10 +-
 .../webauthn/storage/CredentialRegistration.java   |  90 +--------
 .../storage/EnhancedCredentialRegistration.java    | 207 +++++++++++++++++++++
 .../CreatePublicKeyCredentialCreationOptions.java  |   2 +-
 ...a => ExtractKeyInformationFromFormRequest.java} |  10 +-
 .../admin/impl/LookupCredentialsForUser.java       |  20 +-
 .../admin/impl/StorePublicKeyCredential.java       |  28 ---
 .../webauthn/impl/AbstractWebAuthnAction.java      |  30 +++
 .../CreatePublicKeyCredentialRequestOptions.java   |   5 +-
 .../webauthn/impl/LookupRegisteredCredentials.java |  18 +-
 .../authn/webauthn/impl/WebAuthnEncoder.java       |  16 +-
 .../webauthn-management-beans.xml                  |   2 +-
 .../webauthn-management-flow.xml                   |  14 +-
 .../webauthn-registration-beans.xml                |   4 +-
 .../webauthn-registration-flow.xml                 |   2 +-
 .../authn/WebAuthn/webauthn-abstract-beans.xml     |   3 +-
 .../idp/plugin/authn/webauthn/messages.properties  |   2 +
 .../authn/webauthn/views/webauthn-management.vm    |  22 ++-
 .../authn/webauthn/views/webauthn-register.vm      |  10 +-
 ...eatePublicKeyCredentialCreationOptionsTest.java |   4 +-
 ...ctKeyRemovalInformationFromFormRequestTest.java |  10 +-
 .../YubicoWebauthnAuthenticationClientTest.java    |   5 -
 .../webauthn/flow/AbstractWebAuthnFlowTest.java    |   2 -
 .../webauthn/flow/TestAdminManagementFlow.java     |   4 +-
 .../authn/webauthn/flow/TestRegistrationFlow.java  |   6 +-
 .../authn/webauthn/impl/AbstractWebAuthnTest.java  |   1 -
 ...kupRegisteredCredentialsFromUserHandleTest.java |   3 -
 .../impl/LookupRegisteredCredentialsTest.java      |   2 -
 .../impl/CredentialRegistrationSerializerTest.java |   1 -
 ...IdPStorageServiceCredentialRespositoryTest.java |   2 -
 31 files changed, 359 insertions(+), 186 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
index d4c21f9..adb2db3 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
@@ -23,7 +23,7 @@ import org.opensaml.messaging.context.BaseContext;
 
 import com.yubico.webauthn.data.UserVerificationRequirement;
 
-import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRegistration;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -44,7 +44,7 @@ public class BaseWebAuthnContext extends BaseContext {
      * Credentials that have already been registered with the IdP. The authenticator should use these to avoid creating
      * duplicate credentials during registration, or to tell the browser which credentials to use during authentication.
      */
-    @Nullable private Collection<CredentialRegistration> existingCredentials;
+    @Nullable private Collection<EnhancedCredentialRegistration> existingCredentials;
     
     /** The challenge sent to the authenticator in both registration and authentication ceremonies.*/
     @Nullable private byte[] serverChallenge;
@@ -96,7 +96,7 @@ public class BaseWebAuthnContext extends BaseContext {
      * @return this context
      */
     @Nonnull public BaseWebAuthnContext setExistingCredentials(
-            @Nullable final Collection<CredentialRegistration> credentials) {
+            @Nullable final Collection<EnhancedCredentialRegistration> credentials) {
         existingCredentials = credentials;
         return this;
     }
@@ -106,8 +106,8 @@ public class BaseWebAuthnContext extends BaseContext {
      * 
      * @return the existing credentials.
      */
-    @Nonnull @Unmodifiable @NotLive public Collection<CredentialRegistration> getExistingCredentials() {
-        final Collection<CredentialRegistration> localExistingCredentials = existingCredentials;
+    @Nonnull @Unmodifiable @NotLive public Collection<EnhancedCredentialRegistration> getExistingCredentials() {
+        final Collection<EnhancedCredentialRegistration> localExistingCredentials = existingCredentials;
         if (localExistingCredentials == null) {
             return CollectionSupport.emptyList();
         }
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnManagementContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnManagementContext.java
index bf5a969..7b8ddaf 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnManagementContext.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnManagementContext.java
@@ -21,7 +21,7 @@ import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.BaseContext;
 
-import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRegistration;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -38,7 +38,7 @@ public class WebAuthnManagementContext extends BaseContext {
     @Nullable private String searchUsername;    
     
     /** The located set of credentials registered for the given searchUsername.*/
-    @Nullable @Unmodifiable @NotLive private Collection<CredentialRegistration> foundCredentials;
+    @Nullable @Unmodifiable @NotLive private Collection<EnhancedCredentialRegistration> foundCredentials;
     
     /** The ID of a credential that is going to be removed.*/
     @Nullable private byte[] credentialIdToRemove;  
@@ -95,7 +95,7 @@ public class WebAuthnManagementContext extends BaseContext {
      * @return this context
      */
     @Nonnull public WebAuthnManagementContext setFoundCredentials(
-            @Nullable final Collection<CredentialRegistration> credentials) {
+            @Nullable final Collection<EnhancedCredentialRegistration> credentials) {
         if (credentials == null) {
             foundCredentials = CollectionSupport.emptyList();
         } else {
@@ -109,8 +109,8 @@ public class WebAuthnManagementContext extends BaseContext {
      * 
      * @return the credentials.
      */
-    @Nonnull @Unmodifiable @NotLive public Collection<CredentialRegistration> getFoundCredentials() {
-        final Collection<CredentialRegistration> localFoundCredentials = foundCredentials;
+    @Nonnull @Unmodifiable @NotLive public Collection<EnhancedCredentialRegistration> getFoundCredentials() {
+        final Collection<EnhancedCredentialRegistration> localFoundCredentials = foundCredentials;
         if (localFoundCredentials == null) {
             return CollectionSupport.emptyList();
         }
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRegistration.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRegistration.java
index 94bbc5b..dd69865 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRegistration.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRegistration.java
@@ -18,7 +18,6 @@ import java.time.Instant;
 import java.util.Collections;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import java.util.SortedSet;
 
 import javax.annotation.Nonnull;
@@ -31,7 +30,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
-import com.yubico.fido.metadata.MetadataBLOBPayloadEntry;
 import com.yubico.webauthn.RegisteredCredential;
 import com.yubico.webauthn.data.AuthenticatorTransport;
 import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
@@ -39,7 +37,6 @@ import com.yubico.webauthn.data.UserIdentity;
 
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
@@ -72,9 +69,6 @@ public final class CredentialRegistration {
 
     /** The credential to register. */
     @Nonnull @Unmodifiable @NonnullElements private final RegisteredCredential credential;
-
-    /** Optional attestation metadata about the authenticator. Will be an empty set if not used. */
-    @Nonnull @Unmodifiable @NonnullElements private final Set<MetadataBLOBPayloadEntry> attestationMetadata;
     
     /** The AAGUID of the authenticator.*/
     @Nullable private final byte[] aaguid;
@@ -95,7 +89,6 @@ public final class CredentialRegistration {
         this.credential = builder.credential;
         this.credentialNickname = builder.credentialNickname;
         this.discoverable = builder.discoverable;
-        this.attestationMetadata = builder.attestationMetadata;
         this.userVerified = builder.userVerified;
         this.aaguid = builder.aaguid;
 
@@ -184,17 +177,6 @@ public final class CredentialRegistration {
         return transports;
     }
     
-    /**
-     * Get metadata about an authenticators attestation.
-     * 
-     * @return the metadata about an authenticators attestation
-     */
-    @JsonGetter("attestationMetadata")
-    @Nonnull @Unmodifiable @NonnullElements public Set<MetadataBLOBPayloadEntry> getAttestationMetadata(){
-        return attestationMetadata;
-        
-    }
-    
     /**
      * Get the AAGUID of the authenticator that created this credential. This is optional, for example if attestation
      * is not requested.
@@ -216,53 +198,6 @@ public final class CredentialRegistration {
         return credential.getCredentialId().getBase64Url();
     }
     
-    /**
-     * Get the human-readable, short description of the authenticator (in English) iff the attestation metadata exist.
-     * 
-     * <p>If there is more than one metadata entry, it picks the first it can find with a description.</p>
-     * 
-     * @return the  human-readable, short description of the authenticator (in English), or <code>null</code>.
-     */
-    @JsonIgnore
-    @Nullable public String getAuthenticatorDescription() {
-        if (!attestationMetadata.isEmpty()) {
-            final Optional<Optional<String>> descriptionFound = attestationMetadata.stream()
-                .filter(mtd -> mtd.getMetadataStatement().isPresent())
-                .map(mtd -> mtd.getMetadataStatement().get().getDescription()).findFirst();
-            
-            if (descriptionFound.isEmpty() || descriptionFound.get().isEmpty() ||  
-                    descriptionFound.get().get().isEmpty()) {
-                return null;
-            }
-            return descriptionFound.get().get();            
-        }
-        return null;
-    }
-    
-    /** 
-     * Get a <code>data:</code> URL encoded PNG icon for the authenticator.
-     * 
-     * <p>If there is more than one metadata entry, it picks the first it can find with an icon.</p>
-     * 
-     * @return the icon encoded as a PNG <code>data:</code> URL 
-     */
-    @JsonIgnore
-    @Nullable public String getIcon() {
-        if (!attestationMetadata.isEmpty()) {
-            final Optional<Optional<String>> iconFound = attestationMetadata.stream()
-                .filter(mtd -> mtd.getMetadataStatement().isPresent())
-                .map(mtd -> mtd.getMetadataStatement().get().getIcon()).findFirst();
-            
-            if (iconFound.isEmpty() || iconFound.get().isEmpty() ||  
-                    iconFound.get().get().isEmpty()) {
-                return null;
-            }
-            return iconFound.get().get();            
-        }
-        return null;
-        
-    }
-    
     /**
      * Convert the credential registration into a {@link PublicKeyCredentialDescriptor}.
      * 
@@ -300,9 +235,9 @@ public final class CredentialRegistration {
         final CredentialRegistration other = (CredentialRegistration) obj;
         return Objects.equals(credential, other.credential);
     }
-
+    
     /**
-     * Copy this instance into a new credential registration instance replacing the registered credential with that
+     * Copy this instance into a new credential registration instance replacing he registered credential with that
      * given.
      * 
      * @param newRegisteredCred the new credential
@@ -317,7 +252,6 @@ public final class CredentialRegistration {
                 .withRegistrationTime(registrationTime)
                 // The credential here is the new one
                 .withCredential(newRegisteredCred)
-                .withAttestationMetadata(attestationMetadata)
                 .withAaguid(aaguid)
                 .withCredentialNickname(credentialNickname)
                 .withDiscoverable(discoverable)
@@ -394,15 +328,6 @@ public final class CredentialRegistration {
          */
         @Nonnull public IBuildStage withDiscoverable(@Nonnull final Optional<Boolean> discoverable);
 
-        /**
-         * Set the optional attestation metadata about the authenticator.
-         * 
-         * @param attestationMetadata the metadata
-         * @return the next builder stage
-         */
-        @Nonnull public IBuildStage withAttestationMetadata(
-                @Nonnull final Set<MetadataBLOBPayloadEntry> attestationMetadata);
-
         /**
          * Was the user verified during registration?
          * 
@@ -442,8 +367,6 @@ public final class CredentialRegistration {
         @Nullable private String credentialNickname;
         /** Is the credential a discoverable type.*/
         @Nonnull private Optional<Boolean> discoverable;
-        /** Attestation metadata.*/
-        @Nonnull private Set<MetadataBLOBPayloadEntry> attestationMetadata;
         /** has the user been verified.*/
         private boolean userVerified;        
         /** The AAGUID of the authenticator.*/
@@ -456,7 +379,6 @@ public final class CredentialRegistration {
             discoverable = Optional.empty();            
             userVerified = false;
             transports = Collections.emptySortedSet();
-            attestationMetadata = CollectionSupport.emptySet();
         }
 
         @Override
@@ -506,14 +428,6 @@ public final class CredentialRegistration {
             return this;
         }
 
-        @Override
-        @JsonProperty("attestationMetadata")
-        @Nonnull public IBuildStage withAttestationMetadata(
-                @Nonnull final Set<MetadataBLOBPayloadEntry> attestationMtd) {
-            attestationMetadata = attestationMtd;
-            return this;
-        }
-
         @Override
         @JsonProperty("userVerified")
         @Nonnull public IBuildStage withUserVerified(final boolean isUserVerified) {
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/EnhancedCredentialRegistration.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/EnhancedCredentialRegistration.java
new file mode 100644
index 0000000..e6f6edf
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/EnhancedCredentialRegistration.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.storage;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.yubico.fido.metadata.MetadataBLOBPayloadEntry;
+
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * An ephemeral wrapper class that holds a {@link CredentialRegistration} and any associated metadata. Created and
+ * used during registration or authentication and then discarded. This is not meant to be serialised or stored.
+ */
+public class EnhancedCredentialRegistration {
+    
+    /** The wrapped credential registration.*/
+    @Nonnull private final CredentialRegistration credentialRegistration;
+    
+    /** Optional metadata about the authenticator. Will be an empty set if not used. */
+    @Nonnull @Unmodifiable @NonnullElements @NotLive private final Set<MetadataBLOBPayloadEntry> authenticatorMetadata;
+
+    /**
+     * 
+     * Constructor.
+     *
+     * @param builder the builder to construct this instance from
+     */
+    private EnhancedCredentialRegistration(final Builder builder) {
+        this.credentialRegistration = builder.credentialRegistration;
+        this.authenticatorMetadata = builder.authenticatorMetadata;
+    }
+    
+    /**
+     * Get the wrapped credential registration. 
+     * 
+     * @return the wrapped credentialRegistration.
+     */
+    public CredentialRegistration getCredentialRegistration() {
+        return credentialRegistration;
+    }
+        
+    /**
+     * Get the authenticator metadata.
+     * 
+     * @return Returns the attestationMetadata.
+     */
+    @Nonnull @Unmodifiable @NonnullElements @NotLive
+    public synchronized Set<MetadataBLOBPayloadEntry> getAuthenticatorMetadata() {
+        return authenticatorMetadata;
+    }
+    
+    /**
+     * Get the human-readable, short description of the authenticator (in English) iff the attestation metadata exist.
+     * 
+     * <p>If there is more than one metadata entry, it picks the first it can find with a description.</p>
+     * 
+     * @return the  human-readable, short description of the authenticator (in English), or <code>null</code>.
+     */
+    @Nullable public String getAuthenticatorDescription() {
+        if (!authenticatorMetadata.isEmpty()) {
+            final Optional<Optional<String>> descriptionFound = authenticatorMetadata.stream()
+                .filter(mtd -> mtd.getMetadataStatement().isPresent())
+                .map(mtd -> mtd.getMetadataStatement().get().getDescription()).findFirst();
+            
+            if (descriptionFound.isEmpty() || descriptionFound.get().isEmpty() ||  
+                    descriptionFound.get().get().isEmpty()) {
+                return null;
+            }
+            return descriptionFound.get().get();            
+        }
+        return null;
+    }
+    
+    /** 
+     * Get a <code>data:</code> URL encoded PNG icon for the authenticator.
+     * 
+     * <p>If there is more than one metadata entry, it picks the first it can find with an icon.</p>
+     * 
+     * @return the icon encoded as a PNG <code>data:</code> URL 
+     */
+    @Nullable public String getIcon() {
+        if (!authenticatorMetadata.isEmpty()) {
+            final Optional<Optional<String>> iconFound = authenticatorMetadata.stream()
+                .filter(mtd -> mtd.getMetadataStatement().isPresent())
+                .map(mtd -> mtd.getMetadataStatement().get().getIcon()).findFirst();
+            
+            if (iconFound.isEmpty() || iconFound.get().isEmpty() ||  
+                    iconFound.get().get().isEmpty()) {
+                return null;
+            }
+            return iconFound.get().get();            
+        }
+        return null;
+        
+    }
+
+    /**
+     * The builder.
+     * 
+     * @return the next stage
+     */
+    public static ICredentialRegistrationStage builder() {
+        return new Builder();
+    }
+
+    /**
+     * A builder stage.
+     */
+    public interface ICredentialRegistrationStage {
+        /**
+         * Set the credential registration.
+         * 
+         * @param credentialRegistration the registration
+         * 
+         * @return the next stage
+         */
+        public IBuildStage withCredentialRegistration(@Nonnull final CredentialRegistration credentialRegistration);
+    }
+
+    /**
+     * A builder stage.
+     */
+    public interface IBuildStage {
+        /**
+         * Set the metadata for the authenticator that created the credential.
+         * 
+         * @param authenticatorMetadata the metadata
+         * 
+         * @return the next stage
+         */
+        public IBuildStage withAuthenticatorMetadata(
+                @Nullable final Set<MetadataBLOBPayloadEntry> authenticatorMetadata);
+
+        /** 
+         * Build this object.
+         * 
+         * @return an instance of this object
+         */
+        public EnhancedCredentialRegistration build();
+    }
+
+    /**
+     * The builder.
+     */
+    public static final class Builder implements ICredentialRegistrationStage, IBuildStage {
+        /** The wrapped credential registration.*/
+        @NonnullAfterInit private CredentialRegistration credentialRegistration;        
+        /** Optional metadata about the authenticator. Will be an empty set if not used. */
+        @Nonnull @Unmodifiable @NonnullElements @NotLive 
+        private Set<MetadataBLOBPayloadEntry> authenticatorMetadata;
+
+        /** Constructor.*/
+        private Builder() {
+            authenticatorMetadata = CollectionSupport.emptySet();
+        }
+
+        @Override
+        public IBuildStage withCredentialRegistration(
+                @Nonnull final CredentialRegistration registration) {
+            credentialRegistration = Constraint.isNotNull(registration, "Registration can not be null");
+            return this;
+        }
+
+        @Override
+        public IBuildStage withAuthenticatorMetadata(@Nullable final Set<MetadataBLOBPayloadEntry> metadata) {
+            if (metadata != null) {
+                authenticatorMetadata = CollectionSupport.copyToSet(metadata.stream().filter(Objects::nonNull)
+                        .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get());
+            }
+            return this;
+        }
+
+        @Override
+        public EnhancedCredentialRegistration build() {
+            return new EnhancedCredentialRegistration(this);
+        }
+    }
+
+
+
+    
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
index 4c720e5..805c60e 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
@@ -111,7 +111,7 @@ public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnAc
         try {       
             
             final Set<PublicKeyCredentialDescriptor> existingCredentialDescriptors  = context.getExistingCredentials()
-                    .stream()
+                    .stream().map(cred -> cred.getCredentialRegistration())
                         .map(cred -> cred.toPublicKeyCredentialDescriptor())
                         .filter(Objects::nonNull)
                         .collect(Collectors.toSet());
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractKeyRemovalInformationFromFormRequest.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractKeyInformationFromFormRequest.java
similarity index 92%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractKeyRemovalInformationFromFormRequest.java
rename to webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractKeyInformationFromFormRequest.java
index 859f9dd..3eb6fa6 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractKeyRemovalInformationFromFormRequest.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractKeyInformationFromFormRequest.java
@@ -37,20 +37,20 @@ import net.shibboleth.shared.primitive.StringSupport;
 
 
 /**
- * An action that extracts the credential ID for removal from the incoming HTTP request and uses a {@link BiConsumer}
+ * An action that extracts the credential ID for from the incoming HTTP request and uses a {@link BiConsumer}
  * to set that back onto an appropriate context.
  * 
  * @event {WebAuthnRegistrationEventIds#INVALID_ADMIN_ACTION}
  * @event {EventIds#INVALID_PROFILE_CTX}
  * @post add credential ID to remove to the context
  */
-public class ExtractKeyRemovalInformationFromFormRequest extends AbstractProfileAction {
+public class ExtractKeyInformationFromFormRequest extends AbstractProfileAction {
 
     /** Default credential Id parameter name. */
     @Nonnull @NotEmpty public static final String DEFAULT_PARAMETER_NAME = "credentialId";
     
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractKeyRemovalInformationFromFormRequest.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractKeyInformationFromFormRequest.class);
     
     /** Name of credential Id parameter. */
     @Nonnull @NotEmpty private String credentialIdParameterName;
@@ -59,7 +59,7 @@ public class ExtractKeyRemovalInformationFromFormRequest extends AbstractProfile
     @NonnullAfterInit private BiConsumer<ProfileRequestContext, byte[]> contextSettingConsumer;
 
     /** Constructor. */
-    public ExtractKeyRemovalInformationFromFormRequest() {       
+    public ExtractKeyInformationFromFormRequest() {       
         credentialIdParameterName = DEFAULT_PARAMETER_NAME;
     }
     
@@ -117,7 +117,7 @@ public class ExtractKeyRemovalInformationFromFormRequest extends AbstractProfile
             contextSettingConsumer.accept(profileRequestContext, credentialIdAsBytes);
             log.trace("{} Credential to remove '{}'",getLogPrefix(),credentialId);
         } catch (final DecodingException e) {
-            log.debug("{} Unable to base64 decode credentialID, can not remove credential", getLogPrefix());
+            log.debug("{} Unable to base64 decode credentialID, can not set credential", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_ADMIN_ACTION);
             return;
         }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java
index a9d4dab..f207f24 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java
@@ -15,6 +15,8 @@
 package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 
 import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
 
 import javax.annotation.Nonnull;
 
@@ -22,10 +24,14 @@ import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
+import com.yubico.webauthn.data.ByteArray;
+
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext;
 import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRegistration.IBuildStage;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -79,7 +85,19 @@ public class LookupCredentialsForUser extends AbstractWebAuthnAction<WebAuthnMan
             final Collection<CredentialRegistration> credentials = 
                     repository.getRegistrationsByUsername(userToSearchFor);      
             log.debug("{} Found '{}' credentials", getLogPrefix(), credentials.size());
-            context.setFoundCredentials(credentials);
+            
+            final Collection<EnhancedCredentialRegistration> enhancedCredentialRegistrations = 
+                    new HashSet<>(credentials.size());
+            
+            credentials.stream().filter(Objects::nonNull).forEach(cred -> {
+                final IBuildStage builder = EnhancedCredentialRegistration.builder().withCredentialRegistration(cred);
+                if (cred.getAaguid() != null) {
+                    builder.withAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(cred.getAaguid())));
+                }
+                enhancedCredentialRegistrations.add(builder.build());
+            });
+            
+            context.setFoundCredentials(enhancedCredentialRegistrations);
         }
 
     }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java
index cfcfbb6..aae9ed7 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java
@@ -16,7 +16,6 @@ package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 
 import java.time.Instant;
 import java.util.Optional;
-import java.util.Set;
 import java.util.TreeSet;
 
 import javax.annotation.Nonnull;
@@ -26,9 +25,6 @@ import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import com.yubico.fido.metadata.AAGUID;
-import com.yubico.fido.metadata.FidoMetadataService;
-import com.yubico.fido.metadata.MetadataBLOBPayloadEntry;
 import com.yubico.webauthn.RegisteredCredential;
 import com.yubico.webauthn.data.ByteArray;
 import com.yubico.webauthn.data.UserIdentity;
@@ -40,11 +36,8 @@ import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationCont
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NonnullElements;
-import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.EncodingException;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -138,7 +131,6 @@ public class StorePublicKeyCredential extends AbstractWebAuthnAuditingAction<Web
                     .withRegistrationTime(now)
                     .withCredential(credential)
                     .withAaguid(aaguid != null ? aaguid.getBytes() : null)
-                    .withAttestationMetadata(getAttestationMetadata(registrationResult.getAaguid()))
                     .withCredentialNickname(context.getCredentialNickname())
                     .withDiscoverable(isDiscoverable)
                     .withUserVerified(registrationResult.isUserVerified())
@@ -177,25 +169,5 @@ public class StorePublicKeyCredential extends AbstractWebAuthnAuditingAction<Web
     
     // Checkstyle: MethodLength ON
 
-    /**
-     * Find attestation metadata for the authenticator. 
-     * 
-     * @param authenticatorId the authenticator attestation GUID
-     * 
-     * @return the attestation metadata relating to the authenticator attestation GUID
-     */
-    @Nonnull @NotLive @NonnullElements private Set<MetadataBLOBPayloadEntry> getAttestationMetadata(
-            final ByteArray authenticatorId) {
-        final FidoMetadataService localMetadataService = getFidoMetadataService();
-        if (localMetadataService != null) {
-            final Set<MetadataBLOBPayloadEntry> found =
-                    localMetadataService.findEntries(new AAGUID(authenticatorId));
-            if (found == null) {
-                return CollectionSupport.emptySet();
-            }
-            return CollectionSupport.copyToSet(found);
-        }
-        return CollectionSupport.emptySet();
-    }
 
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java
index 24034fb..190db93 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
+import java.util.Set;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -24,7 +25,10 @@ import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
+import com.yubico.fido.metadata.AAGUID;
 import com.yubico.fido.metadata.FidoMetadataService;
+import com.yubico.fido.metadata.MetadataBLOBPayloadEntry;
+import com.yubico.webauthn.data.ByteArray;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.client.WebAuthnAuthenticationClient;
@@ -32,7 +36,10 @@ import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialReposi
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -221,5 +228,28 @@ public class AbstractWebAuthnAction<T> extends AbstractProfileAction {
         // Default does nothing
     }
     
+    
+
+    /**
+     * Find metadata for the authenticator. 
+     * 
+     * @param authenticatorId the authenticator attestation GUID
+     * 
+     * @return the attestation metadata relating to the authenticator attestation GUID
+     */
+    @Nonnull @NotLive @NonnullElements protected Set<MetadataBLOBPayloadEntry> getAuthenticatorMetadata(
+            final ByteArray authenticatorId) {
+        final FidoMetadataService localMetadataService = getFidoMetadataService();
+        if (localMetadataService != null) {
+            final Set<MetadataBLOBPayloadEntry> found =
+                    localMetadataService.findEntries(new AAGUID(authenticatorId));
+            if (found == null) {
+                return CollectionSupport.emptySet();
+            }
+            return CollectionSupport.copyToSet(found);
+        }
+        return CollectionSupport.emptySet();
+    }
+    
 
 }
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 4b9838f..14683cd 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
@@ -37,7 +37,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.authn.CredentialRequestOptionsPa
 import net.shibboleth.idp.plugin.authn.webauthn.client.WebAuthnAuthenticationClient;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.exception.WebAuthnAuthenticationClientException;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRegistration;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -79,8 +79,9 @@ public class CreatePublicKeyCredentialRequestOptions extends AbstractWebAuthnAct
         }
                 
         try {
-            final Collection<CredentialRegistration> existingCredentials = context.getExistingCredentials();
+            final Collection<EnhancedCredentialRegistration> existingCredentials = context.getExistingCredentials();
             final List<PublicKeyCredentialDescriptor> existingCredentialDescriptors  = existingCredentials.stream()
+                .map(cred -> cred.getCredentialRegistration())
                 .map(cred -> cred.toPublicKeyCredentialDescriptor())
                 .filter(Objects::nonNull)
                 .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableList())).get();
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
index e48032f..38ca464 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
@@ -15,6 +15,8 @@
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
 import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Predicate;
 
@@ -33,6 +35,8 @@ import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.authn.WebAuthnAuthenticationEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRegistration.IBuildStage;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
@@ -134,7 +138,18 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnAction<BaseWebA
                 repository.getRegistrationsByUsername(username);   
 
         log.debug("{} Found '{}' registered credentials for '{}'", getLogPrefix(), credentials.size(), username);
-        context.setExistingCredentials(credentials);
+        
+        final Collection<EnhancedCredentialRegistration> enhancedCredentialRegistrations = 
+                new HashSet<>(credentials.size());
+        
+        credentials.stream().filter(Objects::nonNull).forEach(cred -> {
+            final IBuildStage builder = EnhancedCredentialRegistration.builder().withCredentialRegistration(cred);
+            if (cred.getAaguid() != null) {
+                builder.withAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(cred.getAaguid())));
+            }
+            enhancedCredentialRegistrations.add(builder.build());
+        });
+        context.setExistingCredentials(enhancedCredentialRegistrations);
         
         final Optional<ByteArray> userHandle = repository.getUserHandleForUsername(username);
         if (userHandle.isPresent()) {
@@ -150,4 +165,5 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnAction<BaseWebA
         } 
     }
 
+
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/WebAuthnEncoder.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/WebAuthnEncoder.java
index f8dcf46..3b89e7e 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/WebAuthnEncoder.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/WebAuthnEncoder.java
@@ -32,6 +32,7 @@ import com.yubico.webauthn.data.AuthenticatorTransport;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
 import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
 
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRegistration;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -129,6 +130,19 @@ public final class WebAuthnEncoder {
         return formatted;
     }
     
+    /**
+     * Does the credential have attached metadata.
+     * 
+     * @param cred the credential to check
+     * @return true if the credential has metadata, false otherwise.
+     */
+    public static boolean isAuthenticatorMetadataAttached(final EnhancedCredentialRegistration cred) {
+        if (cred == null) {
+            return false;
+        }
+        return !cred.getAuthenticatorMetadata().isEmpty();
+    }
+    
     /**
      * Convert a set of {@link AuthenticatorTransport transports} into a CSV string.
      * 
@@ -136,7 +150,7 @@ public final class WebAuthnEncoder {
      * 
      * @return the CSV string
      */
-    public static String formatTransports(@Nullable final Set<AuthenticatorTransport> transports) {
+    @Nonnull public static String formatTransports(@Nullable final Set<AuthenticatorTransport> transports) {
         if (transports == null) {
             return "";
         }        
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml
index 11ffcf8..2da5559 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml
@@ -74,7 +74,7 @@
         p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnManagementContext" />
         
     <bean id="ExtractKeyRemovalInformationFromFormRequest" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyRemovalInformationFromFormRequest"
+        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyInformationFromFormRequest"
         p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
         <property name="contextSettingConsumer">
             <bean class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ManagementContextCredentialRemovalConsumer"/>
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-flow.xml
index 4e3b336..98192cc 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-flow.xml
@@ -59,13 +59,19 @@
      <!-- Call the c14n subflow here so we can c14n the search username to lookup the correct credentials -->
     <subflow-state id="CallSubjectCanonicalization" subflow="c14n">
         <input name="calledAsSubflow" value="true" />
-        <transition on="proceed" to="LookupCredentials" />
+        <transition on="proceed" to="UpdateSearchName" />
 
-        <transition on="SubjectCanonicalizationError" to="ReselectFlow" />
+        <transition on="SubjectCanonicalizationError" to="InvalidSubjectCanonicalizationContext" />
     </subflow-state>
     
-    <action-state id="LookupCredentials">
-       <evaluate expression="UpdateAdminSearchUsernameWithC14nPrincipal"/>
+    <action-state id="UpdateSearchName">
+        <evaluate expression="UpdateAdminSearchUsernameWithC14nPrincipal"/>
+        
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="LookupCredentials" />      
+    </action-state>
+    
+    <action-state id="LookupCredentials">       
        <evaluate expression="LookupCredentialsForUser"/>   
       
         <evaluate expression="'proceed'" />
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 0793883..7904d4a 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
@@ -58,7 +58,7 @@
     
     <bean id="InitializeSubjectCanonicalizationContext" parent="AbstractWebAuthnBaseAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.InitializeSubjectCanonicalizationContext" scope="prototype"
-         p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"/>
+        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"/>
     
     <bean id="PopulateSubjectCanonicalizationContext" 
         class="net.shibboleth.idp.authn.impl.PopulateSubjectCanonicalizationContext" scope="prototype"
@@ -144,7 +144,7 @@
         p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
 
     <bean id="ExtractKeyRemovalInformationFromFormRequest" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyRemovalInformationFromFormRequest"
+        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyInformationFromFormRequest"
         p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
         <property name="contextSettingConsumer">
             <bean class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.RegistrationContextCredentialRemovalConsumer"/>
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
index 25e1f80..3e7e9ac 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
@@ -65,7 +65,7 @@
         <input name="calledAsSubflow" value="true" />
         <transition on="proceed" to="LookupRegisteredCredentials" />
 
-        <transition on="SubjectCanonicalizationError" to="ReselectFlow" />
+        <transition on="SubjectCanonicalizationError" to="InvalidSubjectCanonicalizationContext" />
     </subflow-state>
     
     <!-- After we canonicalize the username input, use that to lookup registered credentials -->
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-abstract-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-abstract-beans.xml
index f68b950..3f5b8a2 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-abstract-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-abstract-beans.xml
@@ -30,7 +30,8 @@
       
     <bean id="AbstractWebAuthnBaseAction" scope="prototype" abstract="true"
         p:webAuthnClient="#{getObject('shibboleth.authn.webauthn.DefaultWebAuthnAuthenticationClientFactory')}"
-        p:credentialRepository="#{getObject('shibboleth.authn.webauthn.DefaultCredentialRepository')}"/>
+        p:credentialRepository="#{getObject('shibboleth.authn.webauthn.DefaultCredentialRepository')}"
+        p:fidoMetadataService="#{'false'.equals('%{idp.authn.webauthn.metadata.enabled:false}') ? null : getObject('shibboleth.authn.webauthn.DefaultWebAuthnFidoMetadataServiceFactory')}"/>
     
      <!-- Used in views to calculate CSP hashes and nonces, remove and adjust beans in flow when compatibility bumped past 5.0 -->
     
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties
index 48ff132..9f07636 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties
@@ -48,6 +48,8 @@ idp.webauthn.admin.table.header.registrationTime = Registration Time
 idp.webauthn.admin.table.header.action = Action
 idp.webauthn.admin.noKeys  = There are no registered keys
 idp.webauthn.admin.unsupported = Your browser is not WebAuthn compatible
+idp.webauthn.admin.table.hasMetadata = Yes
+idp.webauthn.admin.table.noMetadata = No
 
 idp.webauthn.ended = Your session has ended
 
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management.vm
index f6ef14a..3085202 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management.vm
@@ -58,18 +58,17 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
                      #if ($webAuthnManContext.foundCredentials)
                          <table>
                             <tr>
-                               <th>#springMessageText("idp.webauthn.admin.table.header.userName", "User")</th>
                                <th>#springMessageText("idp.webauthn.admin.table.header.keyName", "Key Name")</th>
                                <th>#springMessageText("idp.webauthn.admin.table.header.authenticatorDescription", "Authenticator")</th>
                                <th>#springMessageText("idp.webauthn.admin.table.header.transports", "Transports")</th>
                                <th>#springMessageText("idp.webauthn.admin.table.header.passkey", "Passkey?")</th>
                                <th>#springMessageText("idp.webauthn.admin.table.header.registrationTime", "Registration Time")</th>
+                               <th>#springMessageText("idp.webauthn.admin.table.header.hasMetadata", "Metadata?")</th>
                                <th>#springMessageText("idp.webauthn.admin.table.header.action", "Action")</th>
                             </tr>
                             #foreach($cred in $webAuthnManContext.foundCredentials)
                                 <tr>
-                                   <td>$encoder.encodeForHTML($cred.username)</td>
-                                   <td>$encoder.encodeForHTML($cred.nickname)</td>
+                                   <td>$encoder.encodeForHTML($cred.credentialRegistration.nickname)</td>
                                    #if ($cred.authenticatorDescription)
                                        <td>
                                        #if ($cred.icon)
@@ -79,16 +78,23 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
                                    #else
                                         <td>#springMessageText("idp.webauthn.admin.table.unknownCredential", "unknown")</td>
                                    #end
-                                   <td>$encoder.encodeForHTML($webAuthnEncoder.formatTransports($cred.transports))</td>
-                                   <td>$encoder.encodeForHTML($webAuthnEncoder.formatDiscoverable($cred.isDiscoverable()))</td>
-                                   <td>$encoder.encodeForHTML($webAuthnEncoder.formatInstant($cred.registrationTime))</td>
+                                   <td>$encoder.encodeForHTML($webAuthnEncoder.formatTransports($cred.credentialRegistration.transports))</td>
+                                   <td>$encoder.encodeForHTML($webAuthnEncoder.formatDiscoverable($cred.credentialRegistration.isDiscoverable()))</td>
+                                   <td>$encoder.encodeForHTML($webAuthnEncoder.formatInstant($cred.credentialRegistration.registrationTime))</td>
+                                   <td>
+                                        #if ($webAuthnEncoder.isAuthenticatorMetadataAttached($cred))
+                                           #springMessageText("idp.webauthn.admin.table.hasMetadata", "Yes") 
+                                       #else
+                                           #springMessageText("idp.webauthn.admin.table.noMetadata", "No")
+                                       #end
+                                   </td>
                                    <td>
                                       <form id="delete_key_form" action="$flowExecutionUrl" method="post">
                                          #parse("csrf/csrf.vm")
-                                         <input type="hidden" name="credentialId" value="$cred.credentialIdBase64Url"/>
+                                         <input type="hidden" name="credentialId" value="$cred.credentialRegistration.credentialIdBase64Url"/>
                                          <button class="webauthn-table-button" onclick="$areYouSure" id="removeButton" type="submit" name="_eventId_deleteKey">
                                         #springMessageText("idp.webauthn.admin.credential.remove", "Remove")</button>
-                                      </form>
+                                      </form>                                      
                                    </td>
                                 </tr>
                              #end 
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
index ef1136d..cf65399 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
@@ -134,7 +134,7 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
                             </tr>
                             #foreach($cred in $webauthnRegContext.existingCredentials)
                             <tr>
-                               <td>$encoder.encodeForHTML($cred.nickname)</td>
+                               <td>$encoder.encodeForHTML($cred.credentialRegistration.nickname)</td>
                                #if ($cred.authenticatorDescription)
                                    <td>
                                    #if ($cred.icon)
@@ -144,13 +144,13 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
                                #else
                                     <td>#springMessageText("idp.webauthn.register.table.unknownCredential", "unknown")</td>
                                #end
-                               <td>$encoder.encodeForHTML($webAuthnEncoder.formatTransports($cred.transports))</td>
-                               <td>$encoder.encodeForHTML($webAuthnEncoder.formatDiscoverable($cred.isDiscoverable()))</td>
-                               <td>$encoder.encodeForHTML($webAuthnEncoder.formatInstant($cred.registrationTime))</td>
+                               <td>$encoder.encodeForHTML($webAuthnEncoder.formatTransports($cred.credentialRegistration.transports))</td>
+                               <td>$encoder.encodeForHTML($webAuthnEncoder.formatDiscoverable($cred.credentialRegistration.isDiscoverable()))</td>
+                               <td>$encoder.encodeForHTML($webAuthnEncoder.formatInstant($cred.credentialRegistration.registrationTime))</td>
                                <td>
                                   <form id="delete_key_form" action="$flowExecutionUrl" method="post">
                                      #parse("csrf/csrf.vm")
-                                     <input type="hidden" name="credentialId" value="$cred.credentialIdBase64Url"/>
+                                     <input type="hidden" name="credentialId" value="$cred.credentialRegistration.credentialIdBase64Url"/>
                                      <button class="webauthn-table-button" onclick="$areYouSure" id="removeButton" type="submit" name="_eventId_deleteKey">
                                     #springMessageText("idp.webauthn.register.credential.remove", "Remove")</button>
                                   </form>
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptionsTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptionsTest.java
index 40a3377..bcadebf 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptionsTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptionsTest.java
@@ -31,6 +31,7 @@ import com.yubico.webauthn.data.UserVerificationRequirement;
 import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRegistration;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 
@@ -95,7 +96,8 @@ public class CreatePublicKeyCredentialCreationOptionsTest extends AbstractWebAut
         context.setServerChallenge(generateRandomBytes(17));
         action.initialize();
 
-        context.setExistingCredentials(CollectionSupport.setOf(createCredentialRegistration()));
+        context.setExistingCredentials(CollectionSupport.setOf(EnhancedCredentialRegistration.builder()
+                .withCredentialRegistration(createCredentialRegistration()).build()));
         
         final Event result = action.execute(src);
         assertNull(result);
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractKeyRemovalInformationFromFormRequestTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractKeyRemovalInformationFromFormRequestTest.java
index ad28e8e..b9e35d0 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractKeyRemovalInformationFromFormRequestTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractKeyRemovalInformationFromFormRequestTest.java
@@ -30,11 +30,11 @@ import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.testing.ConstantSupplier;
 
 /**
- * Tests for {@link ExtractKeyRemovalInformationFromFormRequest}
+ * Tests for {@link ExtractKeyInformationFromFormRequest}
  */
 public class ExtractKeyRemovalInformationFromFormRequestTest extends AbstractWebAuthnTest {
     
-    private ExtractKeyRemovalInformationFromFormRequest action;
+    private ExtractKeyInformationFromFormRequest action;
     
     private WebAuthnRegistrationContext context;
     
@@ -48,7 +48,7 @@ public class ExtractKeyRemovalInformationFromFormRequestTest extends AbstractWeb
         
         request = new MockHttpServletRequest();
         
-        action = new ExtractKeyRemovalInformationFromFormRequest();
+        action = new ExtractKeyInformationFromFormRequest();
     } 
     
     @Test
@@ -59,7 +59,7 @@ public class ExtractKeyRemovalInformationFromFormRequestTest extends AbstractWeb
         final byte[] credentialIdBytes = generateRandomBytes(16);
         final String credentialIdb64 = Base64Support.encodeURLSafe(credentialIdBytes);
         
-        request.addParameter(ExtractKeyRemovalInformationFromFormRequest.DEFAULT_PARAMETER_NAME, 
+        request.addParameter(ExtractKeyInformationFromFormRequest.DEFAULT_PARAMETER_NAME, 
                 new String[]{credentialIdb64});
         action.setHttpServletRequestSupplier(new ConstantSupplier<>(request));
         action.initialize();
@@ -90,7 +90,7 @@ public class ExtractKeyRemovalInformationFromFormRequestTest extends AbstractWeb
         final SimpleContext contextToUpdate = new SimpleContext();
         action.setContextSettingConsumer((prc, bytes) -> contextToUpdate.setCredentialIdToRemove(bytes));
 
-        request.addParameter(ExtractKeyRemovalInformationFromFormRequest.DEFAULT_PARAMETER_NAME, 
+        request.addParameter(ExtractKeyInformationFromFormRequest.DEFAULT_PARAMETER_NAME, 
                 new String[]{"not-encoded"});
         action.setHttpServletRequestSupplier(new ConstantSupplier<>(request));
         action.initialize();
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 460214c..698a762 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
@@ -49,7 +49,6 @@ import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.InMemoryRegistrationStorage;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
 import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.shared.collection.CollectionSupport;
 
 /**
  * Tests for {@link YubicoWebAuthnAuthenticationClient}. To some extent this is testing the Yubico libraries work
@@ -188,7 +187,6 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
                 .withTransports(new TreeSet<AuthenticatorTransport>())
                 .withRegistrationTime(Instant.now())
                 .withCredential(credential)
-                .withAttestationMetadata(CollectionSupport.emptySet())
                 .withCredentialNickname("Nickname")
                 .withDiscoverable(Optional.of(Boolean.TRUE))
                 .withUserVerified(true)
@@ -261,7 +259,6 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
                 .withTransports(new TreeSet<AuthenticatorTransport>())
                 .withRegistrationTime(Instant.now())
                 .withCredential(credential)
-                .withAttestationMetadata(CollectionSupport.emptySet())
                 .withCredentialNickname("Nickname")
                 .withDiscoverable(Optional.of(Boolean.TRUE))
                 .withUserVerified(true)
@@ -306,7 +303,6 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
                 .withTransports(new TreeSet<AuthenticatorTransport>())
                 .withRegistrationTime(Instant.now())
                 .withCredential(credential)
-                .withAttestationMetadata(CollectionSupport.emptySet())
                 .withCredentialNickname("Nickname")
                 .withDiscoverable(Optional.of(Boolean.TRUE))
                 .withUserVerified(true)
@@ -352,7 +348,6 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
                 .withTransports(new TreeSet<AuthenticatorTransport>())
                 .withRegistrationTime(Instant.now())
                 .withCredential(credential)
-                .withAttestationMetadata(CollectionSupport.emptySet())
                 .withCredentialNickname("Nickname")
                 .withDiscoverable(Optional.of(Boolean.TRUE))
                 .withUserVerified(true)
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
index a2ccfbc..d8c8705 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
@@ -92,7 +92,6 @@ import net.shibboleth.idp.ui.context.RelyingPartyUIContext;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.EncodingException;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.servlet.impl.HttpServletRequestResponseContext;
 
@@ -243,7 +242,6 @@ public class AbstractWebAuthnFlowTest extends AbstractFlowTest {
                  .withTransports(new TreeSet<AuthenticatorTransport>())
                  .withRegistrationTime(Instant.now())
                  .withCredential(credential)
-                 .withAttestationMetadata(CollectionSupport.emptySet())
                  .withCredentialNickname("nickname")
                  .withDiscoverable(Optional.of(Boolean.TRUE))
                  .withUserVerified(true)
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java
index 27890ee..fd84f63 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java
@@ -34,7 +34,7 @@ import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
 import com.yubico.webauthn.data.PublicKeyCredential;
 
 import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyRemovalInformationFromFormRequest;
+import net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyInformationFromFormRequest;
 import net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractUsernameSearchFromFormRequest;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.impl.ExtractPublicKeyCredentialAssertionFromFormRequest;
@@ -98,7 +98,7 @@ public class TestAdminManagementFlow extends AbstractWebAuthnFlowTest{
         assertCurrentStateEquals("ManagementView", result.getSecond());
         // Re-set external context to holder
         ExternalContextHolder.setExternalContext(externalContext);
-        setHttpFormRequest("POST", Map.of(ExtractKeyRemovalInformationFromFormRequest.DEFAULT_PARAMETER_NAME, 
+        setHttpFormRequest("POST", Map.of(ExtractKeyInformationFromFormRequest.DEFAULT_PARAMETER_NAME, 
                 registration.getCredentialIdBase64Url()));
         externalContext.setEventId("deleteKey");
         // Add c14n context to simulate c14n flows
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
index 9fadeb5..5ac9de8 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
@@ -38,7 +38,7 @@ import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
 import com.yubico.webauthn.data.RegistrationExtensionInputs;
 import com.yubico.webauthn.data.UserIdentity;
 
-import net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyRemovalInformationFromFormRequest;
+import net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyInformationFromFormRequest;
 import net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractPublicKeyCredentialAttestationFromFormRequest;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
@@ -91,7 +91,7 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
         
         // Re-set external context to holder
         ExternalContextHolder.setExternalContext(externalContext);
-        setHttpFormRequest("POST", Map.of(ExtractKeyRemovalInformationFromFormRequest.DEFAULT_PARAMETER_NAME,
+        setHttpFormRequest("POST", Map.of(ExtractKeyInformationFromFormRequest.DEFAULT_PARAMETER_NAME,
                 registration.getCredentialIdBase64Url()));
         externalContext.setEventId("deleteKey");
         result.getSecond().setCurrentState("DisplayWebAuthnRegistrationView");
@@ -136,7 +136,7 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
         
         // Re-set external context to holder
         ExternalContextHolder.setExternalContext(externalContext);
-        setHttpFormRequest("POST", Map.of(ExtractKeyRemovalInformationFromFormRequest.DEFAULT_PARAMETER_NAME,
+        setHttpFormRequest("POST", Map.of(ExtractKeyInformationFromFormRequest.DEFAULT_PARAMETER_NAME,
                 registrationAnotherUser.getCredentialIdBase64Url()));
         externalContext.setEventId("deleteKey");
         result.getSecond().setCurrentState("DisplayWebAuthnRegistrationView");
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 f826daa..62a8e8f 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
@@ -348,7 +348,6 @@ public abstract class AbstractWebAuthnTest {
                  .withTransports(new TreeSet<AuthenticatorTransport>())
                  .withRegistrationTime(Instant.now())
                  .withCredential(credential)
-                 .withAttestationMetadata(CollectionSupport.emptySet())
                  .withCredentialNickname("nickname")
                  .withDiscoverable(Optional.of(Boolean.TRUE))
                  .withUserVerified(true)
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandleTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandleTest.java
index c240d97..fa5a767 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandleTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandleTest.java
@@ -41,7 +41,6 @@ import net.shibboleth.idp.plugin.authn.webauthn.authn.WebAuthnAuthenticationEven
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
 import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.shared.collection.CollectionSupport;
 
 /**
  * Tests for {@link LookupRegisteredCredentialsFromUserHandle}
@@ -97,7 +96,6 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
                 .withTransports(new TreeSet<AuthenticatorTransport>())
                 .withRegistrationTime(Instant.now())
                 .withCredential(credential)
-                .withAttestationMetadata(CollectionSupport.emptySet())
                 .withCredentialNickname("Nickname")
                 .withDiscoverable(Optional.of(Boolean.TRUE))
                 .withUserVerified(true)
@@ -152,7 +150,6 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
                 .withTransports(new TreeSet<AuthenticatorTransport>())
                 .withRegistrationTime(Instant.now())
                 .withCredential(credential)
-                .withAttestationMetadata(CollectionSupport.emptySet())
                 .withCredentialNickname("Nickname")
                 .withDiscoverable(Optional.of(Boolean.TRUE))
                 .withUserVerified(true)
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
index 4ed0f21..be0c33b 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
@@ -41,7 +41,6 @@ import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
 import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 
 /**
@@ -94,7 +93,6 @@ public class LookupRegisteredCredentialsTest extends AbstractWebAuthnTest {
                  .withTransports(new TreeSet<AuthenticatorTransport>())
                  .withRegistrationTime(Instant.now())
                  .withCredential(credential)
-                 .withAttestationMetadata(CollectionSupport.emptySet())
                  .withCredentialNickname("nickname")
                  .withDiscoverable(Optional.of(Boolean.TRUE))
                  .withUserVerified(true)
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java
index 3536138..0b46f10 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java
@@ -82,7 +82,6 @@ public class CredentialRegistrationSerializerTest extends AbstractWebAuthnTest {
                  .withTransports(new TreeSet<AuthenticatorTransport>())
                  .withRegistrationTime(Instant.now())
                  .withCredential(credential)
-                 .withAttestationMetadata(CollectionSupport.emptySet())
                  .withCredentialNickname("nickname")
                  .withDiscoverable(Optional.of(Boolean.TRUE))
                  .withUserVerified(true)
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java
index b185b5c..fa82eba 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java
@@ -44,7 +44,6 @@ import com.yubico.webauthn.data.UserIdentity;
 
 import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
-import net.shibboleth.shared.collection.CollectionSupport;
 
 /**
  * Tests for {@link IdPStorageServiceCredentialRespository}.
@@ -103,7 +102,6 @@ public class IdPStorageServiceCredentialRespositoryTest extends AbstractWebAuthn
                 .withTransports(new TreeSet<AuthenticatorTransport>())
                 .withRegistrationTime(Instant.now())
                 .withCredential(credential)
-                .withAttestationMetadata(CollectionSupport.emptySet())
                 .withCredentialNickname("nickname")
                 .withDiscoverable(Optional.of(Boolean.TRUE))
                 .withUserVerified(true)

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


More information about the commits mailing list