[java-idp-plugin-webauthn] branch main updated: JWEBAUTHN-28 - Support software authenticator AAGUID UI descriptions

Phil Smart philip.smart at jisc.ac.uk
Thu Sep 26 14:56:41 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=f5328b8cedd6ad58379addfad2398aaa4187085b

The following commit(s) were added to refs/heads/main by this push:
     new f5328b8  JWEBAUTHN-28 - Support software authenticator AAGUID UI descriptions
f5328b8 is described below

commit f5328b8cedd6ad58379addfad2398aaa4187085b
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Sep 26 15:46:50 2024 +0100

    JWEBAUTHN-28 - Support software authenticator AAGUID UI descriptions
    
     - Add support for an aaguid JSON file that can be loaded to add icon
    and description to those authenticators/providers not found in the MDS
    feed.
    
    https://shibboleth.atlassian.net/browse/JWEBAUTHN-28
---
 .../authn/webauthn/metadata/AaguidEntry.java       | 112 ++++++++++++++++++
 .../storage/EnhancedCredentialRegistration.java    |  45 ++++++-
 .../admin/impl/LookupCredentialsForUser.java       |   2 +
 .../webauthn/impl/AbstractWebAuthnAction.java      |  54 ++++++++-
 .../webauthn/impl/LookupRegisteredCredentials.java |   2 +
 .../{ => impl}/FidoMetadataServiceFactory.java     |   2 +-
 .../impl/PasskeyAaguidMetadataFactory.java         |  89 ++++++++++++++
 .../impl/PasskeyAaguidMetadataService.java         |  76 ++++++++++++
 .../webauthn/metadata/{ => impl}/package-info.java |   2 +-
 .../META-INF/net.shibboleth.idp/postconfig.xml     |   8 +-
 .../authn/WebAuthn/webauthn-abstract-beans.xml     |   3 +-
 .../authn/webauthn/conf/authn/webauthn.properties  |   5 +
 .../idp/plugin/authn/webauthn/css/webauthn.css     |   1 +
 .../authn/webauthn/views/webauthn-management.vm    |   2 +-
 .../authn/webauthn/views/webauthn-register.vm      |   2 +-
 .../FidoMetadataServiceResolverFactoryTest.java    |   2 +-
 .../impl/PasskeyAaguidMetadataFactoryTest.java     |  50 ++++++++
 webauthn-impl/src/test/resources/aaguid.json       | 130 +++++++++++++++++++++
 18 files changed, 576 insertions(+), 11 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/AaguidEntry.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/AaguidEntry.java
new file mode 100644
index 0000000..f92e456
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/AaguidEntry.java
@@ -0,0 +1,112 @@
+/*
+ * 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.metadata;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+/** 
+ * A entry in the Passkeys AAGUID JSON file. Used for display purposes only.
+ */
+ at JsonInclude(JsonInclude.Include.NON_NULL)
+ at JsonPropertyOrder({
+    "name",
+    "icon_dark",
+    "icon_light"
+})
+public class AaguidEntry {
+
+    /**
+     * The name of the authenticator/provider.
+     */
+    @JsonProperty("name")
+    private String name;
+    /**
+     *  The base64 SVG data encoded dark icon for this provider
+     */
+    @JsonProperty("icon_dark")
+    @JsonPropertyDescription("")
+    private String iconDark;
+    /**
+     *  The base64 SVG data encoded light icon for this provider
+     */
+    @JsonProperty("icon_light")
+    @JsonPropertyDescription("")
+    private String iconLight;
+
+    /**
+     * Get the name of the authenticator/provider.
+     * 
+     * return the name
+     */
+    @JsonProperty("name")
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set the name of the authenticator/provider.
+     * 
+     * @param name the name of the authenticator/provider
+     */
+    @JsonProperty("name")
+    public void setName(final String nameIn) {
+        name = nameIn;
+    }
+
+    /**
+     * Get the base64 SVG data encoded dark icon for this provider.
+     * 
+     * @return the icon
+     */
+    @JsonProperty("icon_dark")
+    public String getIconDark() {
+        return iconDark;
+    }
+
+    /**
+     * Set the base64 SVG data encoded dark icon for this provider.
+     * 
+     * @param icon the dark icon
+     */
+    @JsonProperty("icon_dark")
+    public void setIconDark(final String icon) {
+        iconDark = icon;
+    }
+
+    /**
+     * Get the base64 SVG data encoded light icon for this provider.
+     * 
+     * @return the light icon
+     */
+    @JsonProperty("icon_light")
+    public String getIconLight() {
+        return iconLight;
+    }
+
+    /**
+     * Set the base64 SVG data encoded light icon for this provider.
+     * 
+     * @param icon the light icon
+     */
+    @JsonProperty("icon_light")
+    public void setIconLight(final String icon) {
+        this.iconLight = icon;
+    }
+
+}
+
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
index 985a320..1437df2 100644
--- 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
@@ -24,6 +24,7 @@ import javax.annotation.Nullable;
 
 import com.yubico.fido.metadata.MetadataBLOBPayloadEntry;
 
+import net.shibboleth.idp.plugin.authn.webauthn.metadata.AaguidEntry;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotLive;
@@ -42,6 +43,12 @@ public final class EnhancedCredentialRegistration {
     
     /** Optional metadata about the authenticator. Will be an empty set if not used. */
     @Nonnull @Unmodifiable @NonnullElements @NotLive private final Set<MetadataBLOBPayloadEntry> authenticatorMetadata;
+    
+    /** 
+     * An AAGUID entry provided by the supplementary JSON file that describes authenticators not in the MDS feed e.g. 
+     * for software providers. Supplies a description and icon for UI only.
+     */
+    @Nullable private final AaguidEntry aaguidMetadata;
 
     /**
      * 
@@ -52,6 +59,7 @@ public final class EnhancedCredentialRegistration {
     private EnhancedCredentialRegistration(final Builder builder) {
         this.credentialRegistration = builder.credentialRegistration;
         this.authenticatorMetadata = builder.authenticatorMetadata;
+        this.aaguidMetadata = builder.aaguidMetadata;
     }
     
     /**
@@ -74,7 +82,8 @@ public final class EnhancedCredentialRegistration {
     }
     
     /**
-     * Get the human-readable, short description of the authenticator (in English) iff the attestation metadata exist.
+     * Get the human-readable, short description of the authenticator (in English) iff either; the attestation metadata 
+     * exists or aaguid metadata exists (preferencing the FIDO2 attestation metadata).
      * 
      * <p>If there is more than one metadata entry, it picks the first it can find with a description.</p>
      * 
@@ -92,13 +101,17 @@ public final class EnhancedCredentialRegistration {
             }
             return descriptionFound.get().get();            
         }
+        if (aaguidMetadata != null) {
+            return aaguidMetadata.getName();
+        }
         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>
+     * <p>If there is more than one metadata entry, it picks the first it can find with an icon. If the 
+     * FIDO2 attestation metadata does not exist, the icon is looked up in the aaguid metadata.</p>
      * 
      * @return the icon encoded as a PNG <code>data:</code> URL 
      */
@@ -114,6 +127,9 @@ public final class EnhancedCredentialRegistration {
             }
             return iconFound.get().get();            
         }
+        if (aaguidMetadata != null) {
+            return aaguidMetadata.getIconLight();
+        }
         return null;
         
     }
@@ -154,6 +170,17 @@ public final class EnhancedCredentialRegistration {
          */
         public IBuildStage withAuthenticatorMetadata(
                 @Nullable final Set<MetadataBLOBPayloadEntry> authenticatorMetadata);
+        
+        /**
+         * Set the metadata for the authenticator from additional sources e.g. for those that do not exist in the
+         * MDS feed such as most software providers.
+         * 
+         * @param aaguidMetadata the metadata
+         * 
+         * @return the next stage
+         */
+        public IBuildStage withAaguidMetadata(
+                @Nullable final AaguidEntry aaguidMetadata);
 
         /** 
          * Build this object.
@@ -172,6 +199,11 @@ public final class EnhancedCredentialRegistration {
         /** Optional metadata about the authenticator. Will be an empty set if not used. */
         @Nonnull @Unmodifiable @NonnullElements @NotLive 
         private Set<MetadataBLOBPayloadEntry> authenticatorMetadata;
+        /** 
+         * An entry provided by the supplementary JSON file that describes authenticators not in the MDS feed e.g. for
+         * software providers. Supplies a description and icon for UI only.
+         */
+        @Nullable private AaguidEntry aaguidMetadata;
 
         /** Constructor.*/
         private Builder() {
@@ -193,11 +225,20 @@ public final class EnhancedCredentialRegistration {
             }
             return this;
         }
+        
+
+        /** {@inheritDoc} */
+        @Override
+        public IBuildStage withAaguidMetadata(@Nullable final AaguidEntry metadata) {
+            aaguidMetadata = metadata;
+            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/LookupCredentialsForUser.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java
index f207f24..dac1fde 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
@@ -24,6 +24,7 @@ import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
+import com.yubico.fido.metadata.AAGUID;
 import com.yubico.webauthn.data.ByteArray;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
@@ -93,6 +94,7 @@ public class LookupCredentialsForUser extends AbstractWebAuthnAction<WebAuthnMan
                 final IBuildStage builder = EnhancedCredentialRegistration.builder().withCredentialRegistration(cred);
                 if (cred.getAaguid() != null) {
                     builder.withAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(cred.getAaguid())));
+                    builder.withAaguidMetadata(getAaguidMetadata(new AAGUID(new ByteArray(cred.getAaguid()))));
                 }
                 enhancedCredentialRegistrations.add(builder.build());
             });
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 190db93..a976bfc 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
@@ -32,6 +32,8 @@ import com.yubico.webauthn.data.ByteArray;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.client.WebAuthnAuthenticationClient;
+import net.shibboleth.idp.plugin.authn.webauthn.metadata.AaguidEntry;
+import net.shibboleth.idp.plugin.authn.webauthn.metadata.impl.PasskeyAaguidMetadataService;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
@@ -77,7 +79,13 @@ public class AbstractWebAuthnAction<T> extends AbstractProfileAction {
     @Nullable private WebAuthnCredentialRepository credentialRepository;
     
     /** Optional FIDO metadata service resolver.*/ 
-    @Nullable private FidoMetadataService fidoMetadataService;  
+    @Nullable private FidoMetadataService fidoMetadataService; 
+  
+    /** 
+     * Supplementary description and icon information for passkey providers not in the MDS feed (mostly software
+     * providers). Enhances the UI.
+     */
+    @Nullable private PasskeyAaguidMetadataService aaguidService;
     
     /**
      * 
@@ -120,6 +128,28 @@ public class AbstractWebAuthnAction<T> extends AbstractProfileAction {
         return webAuthnClient;
     }
     
+    
+    /**
+     * Set the supplementary description service which contains descriptions and icons for passkey providers not in 
+     * the MDS feed.
+     * 
+     * @param service The AAGUID service to set.
+     */
+    public void setAaguidService(@Nullable final PasskeyAaguidMetadataService service) {
+        checkSetterPreconditions();
+        aaguidService = service;
+    }
+    
+    /**
+     * Get the supplementary description service which contains descriptions and icons for passkey providers not in 
+     * the MDS feed.
+     * 
+     * @return the AAGUID service.
+     */
+    @Nullable public PasskeyAaguidMetadataService getAaguidService() {
+        return aaguidService;
+    }
+    
     /**
      * Set the FIDO Alliance metadata service resolver to use as the attestation trust source.
      * 
@@ -231,7 +261,7 @@ public class AbstractWebAuthnAction<T> extends AbstractProfileAction {
     
 
     /**
-     * Find metadata for the authenticator. 
+     * Find FIDO2 MDS metadata for the authenticator. 
      * 
      * @param authenticatorId the authenticator attestation GUID
      * 
@@ -252,4 +282,24 @@ public class AbstractWebAuthnAction<T> extends AbstractProfileAction {
     }
     
 
+    /**
+     * Get basic authenticator/provider information from the AAGUID. Contains providers not in the FIDO2 MDS feed, for
+     * example, software authenticators.
+     * 
+     * @param aaguid the authenticator attestation GUID
+     * @return the aaguid metadata, or null if not found.
+     */
+    @Nullable protected AaguidEntry getAaguidMetadata(@Nullable final AAGUID aaguid) {
+        if (aaguid == null) {
+            return null;
+        }
+        final PasskeyAaguidMetadataService metadataService = getAaguidService();
+        if (metadataService != null) {
+            return metadataService.getEntry(aaguid);
+        }
+        return null;
+    }
+
+    
+
 }
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 38ca464..c638624 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
@@ -28,6 +28,7 @@ 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.webauthn.data.ByteArray;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
@@ -146,6 +147,7 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnAction<BaseWebA
             final IBuildStage builder = EnhancedCredentialRegistration.builder().withCredentialRegistration(cred);
             if (cred.getAaguid() != null) {
                 builder.withAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(cred.getAaguid())));
+                builder.withAaguidMetadata(getAaguidMetadata(new AAGUID(new ByteArray(cred.getAaguid()))));
             }
             enhancedCredentialRegistrations.add(builder.build());
         });
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/FidoMetadataServiceFactory.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/FidoMetadataServiceFactory.java
similarity index 99%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/FidoMetadataServiceFactory.java
rename to webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/FidoMetadataServiceFactory.java
index e8fbf34..71050bd 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/FidoMetadataServiceFactory.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/FidoMetadataServiceFactory.java
@@ -12,7 +12,7 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.plugin.authn.webauthn.metadata;
+package net.shibboleth.idp.plugin.authn.webauthn.metadata.impl;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/PasskeyAaguidMetadataFactory.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/PasskeyAaguidMetadataFactory.java
new file mode 100644
index 0000000..36a3ea7
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/PasskeyAaguidMetadataFactory.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.metadata.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+import org.springframework.beans.FatalBeanException;
+import org.springframework.beans.factory.FactoryBean;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import net.shibboleth.idp.plugin.authn.webauthn.metadata.AaguidEntry;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.resource.Resource;
+
+/**
+ * A spring factory bean for create a {@link PasskeyAaguidMetadataService}. 
+ */
+public class PasskeyAaguidMetadataFactory extends AbstractIdentifiableInitializableComponent 
+    implements FactoryBean<PasskeyAaguidMetadataService> {
+    
+    /** 
+     * The File location of the passkey AAGUID JSON file.
+     */
+    @GuardedBy("this") @Nullable private Resource passkeyAaguidFile;
+    
+    
+    /**
+     * Set the passkey Aaguid JSON file.
+     * 
+     * @param file The passkey Aaguid JSON file to set.
+     */
+    public synchronized void setPasskeyAaguidFile(@Nullable final Resource file) {
+        checkSetterPreconditions();
+        passkeyAaguidFile = file;
+    }
+    
+    /**
+     * Get the passkey Aaguid JSON file
+     * 
+     * @return the passkey Aaguid JSON file.
+     */
+    @Nullable public synchronized Resource getPasskeyAaguidFile() {
+        return passkeyAaguidFile;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    public PasskeyAaguidMetadataService getObject() throws Exception {
+        final ObjectMapper metadataObjMapper = new ObjectMapper();
+        final Resource localAaguidResource = getPasskeyAaguidFile();
+        try {
+            if (localAaguidResource != null) {
+                final Map<String,AaguidEntry> metadataObject = 
+                        metadataObjMapper.readValue(localAaguidResource.getFile(), new TypeReference<HashMap<String, AaguidEntry>>() {});
+                return new PasskeyAaguidMetadataService(metadataObject);
+            } else {
+                throw new FatalBeanException("Can not construct the Passkey AAGUID metadata provider, "
+                        + "no JSON file specified");
+            }
+        } catch (final Exception e) {
+            throw new FatalBeanException("Can not construct the Passkey AAGUID metadata provider", e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Class<?> getObjectType() {
+        return PasskeyAaguidMetadataService.class;
+    }
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/PasskeyAaguidMetadataService.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/PasskeyAaguidMetadataService.java
new file mode 100644
index 0000000..cb6e400
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/PasskeyAaguidMetadataService.java
@@ -0,0 +1,76 @@
+/*
+ * 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.metadata.impl;
+
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.yubico.fido.metadata.AAGUID;
+
+import net.shibboleth.idp.plugin.authn.webauthn.metadata.AaguidEntry;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A service that looks up icons and descriptions about passkey providers (authenticators) from a map.
+ * Mostly, software authenticators that do not exist in the FIDO Metadata Service feed.
+ * 
+ * <p>Is only used for display purposes. Can not be used for trust.</p>
+ */
+public class PasskeyAaguidMetadataService {
+    
+    @Nonnull private final Map<String,AaguidEntry> metadata;
+
+    /**
+     * Constructor.
+     *
+     * @param metadata the map of authenticator attestation GUID to entry.
+     */
+    public PasskeyAaguidMetadataService(@Nonnull final Map<String,AaguidEntry> mtdata) {
+        super();
+        metadata = Constraint.isNotNull(mtdata, "AAGUID Metadata can not be null");
+    }
+    
+    /**
+     * Get a metadata entry based on the AAGUID as a string. It must be in the correct 
+     * 36-character string representation for the lookup to succeed. 
+     *  
+     * @param aaguid the 36-character string AAGUID
+     * 
+     * @return an AAGUID entry, or null if not found.
+     */
+    @Nullable public AaguidEntry getEntry(@Nullable final String aaguid) {
+        if (aaguid == null) {
+            return null;
+        }
+        return metadata.get(aaguid);
+    }
+    
+    /**
+     * Get a metadata entry based on the AAGUID. 
+     *  
+     * @param aaguid the AAGUID of the authenticator/provider
+     * 
+     * @return an AAGUID entry, or null if not found.
+     */
+    @Nullable public AaguidEntry getEntry(@Nullable final AAGUID aaguid) {
+        if (aaguid == null) {
+            return null;
+        }
+        return metadata.get(aaguid.asGuidString());
+    }
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/package-info.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/package-info.java
similarity index 91%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/package-info.java
rename to webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/package-info.java
index 40b04b9..7f3f7db 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/package-info.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/package-info.java
@@ -15,4 +15,4 @@
 /**
  *  This package has the FIDO metadata implementation for WebAuthn.
  */
-package net.shibboleth.idp.plugin.authn.webauthn.metadata;
\ No newline at end of file
+package net.shibboleth.idp.plugin.authn.webauthn.metadata.impl;
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/webauthn-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index de75f02..33eb81a 100644
--- a/webauthn-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -105,7 +105,7 @@
         
     <!-- The optional FIDO Metadata service. Lazy-init, only constructed if used. -->   
     <bean id="shibboleth.authn.webauthn.DefaultWebAuthnFidoMetadataServiceFactory" 
-        class="net.shibboleth.idp.plugin.authn.webauthn.metadata.FidoMetadataServiceFactory" scope="singleton"
+        class="net.shibboleth.idp.plugin.authn.webauthn.metadata.impl.FidoMetadataServiceFactory" scope="singleton"
         lazy-init="true"
         p:trustRootFile="%{idp.authn.webauthn.metadata.trustRootFile:}"
         p:cacheFile="%{idp.authn.webauthn.metadata.cacheFile:}"
@@ -114,6 +114,12 @@
         p:metadataBlobFile="%{idp.authn.webauthn.metadata.metadataBlobFile:}"
         p:expectedLegalHeaders="%{idp.authn.webauthn.metadata.expectedLegalHeaders:Retrieval and use of this BLOB indicates acceptance of the appropriate agreement located at https://fidoalliance.org/metadata/metadata-legal-terms/}"/>
            
+     <!-- The optional supplementry FIDO passkey metadata service. Lazy-init, only constructed if used. -->   
+    <bean id="shibboleth.authn.webauthn.DefaultPasskeyAaguidMetadataServiceFactory" 
+        class="net.shibboleth.idp.plugin.authn.webauthn.metadata.impl.PasskeyAaguidMetadataFactory" scope="singleton"
+        lazy-init="true"
+        p:passkeyAaguidFile="%{idp.authn.webauthn.metadata.aaguid.passkeyAaguidFile:}"/>
+     
      <!-- 
      
         Create a default object mapper. Setup should not change once injected.
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 3f5b8a2..ad48c80 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
@@ -31,7 +31,8 @@
     <bean id="AbstractWebAuthnBaseAction" scope="prototype" abstract="true"
         p:webAuthnClient="#{getObject('shibboleth.authn.webauthn.DefaultWebAuthnAuthenticationClientFactory')}"
         p:credentialRepository="#{getObject('shibboleth.authn.webauthn.DefaultCredentialRepository')}"
-        p:fidoMetadataService="#{'false'.equals('%{idp.authn.webauthn.metadata.enabled:false}') ? null : getObject('shibboleth.authn.webauthn.DefaultWebAuthnFidoMetadataServiceFactory')}"/>
+        p:fidoMetadataService="#{'false'.equals('%{idp.authn.webauthn.metadata.enabled:false}') ? null : getObject('shibboleth.authn.webauthn.DefaultWebAuthnFidoMetadataServiceFactory')}"
+        p:aaguidService="#{'false'.equals('%{idp.authn.webauthn.metadata.aaguid.enabled:false}') ? null : getObject('shibboleth.authn.webauthn.DefaultPasskeyAaguidMetadataServiceFactory')}"/>
     
      <!-- 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/conf/authn/webauthn.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
index e2f1659..b1c57ba 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
@@ -114,6 +114,11 @@ idp.authn.webauthn.supportedPrincipals = \
 ## If you want to load the metadata from a file *only*, set the metadata blob file location. This will override the metadataBlobUrl property
 #idp.authn.webauthn.metadata.metadataBlobFile = %{idp.home}/metadata/blob-mds3.jwt
 
+##### Supplementary AAGUID Metadata
+# To provide an icon and description in the registration interface for those authenticators/providers not in the FIDO metadata feed
+#idp.authn.webauthn.metadata.aaguid.enabled = false
+#idp.authn.webauthn.metadata.aaguid.passkeyAaguidFile = aaguid.json
+
 #### Authentication properties
 
 # Which type of flow is supported? Usernameless or passwordless
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
index 48d6dfe..6e40392 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
@@ -89,4 +89,5 @@ tr:hover {
 
 .authenticator-logo {
     vertical-align: middle;
+    max-width:20%;
 }
\ No newline at end of file
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 3085202..2f56a90 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
@@ -72,7 +72,7 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
                                    #if ($cred.authenticatorDescription)
                                        <td>
                                        #if ($cred.icon)
-                                            <img class="authenticator-logo" src="$encoder.encodeForHTML($cred.icon) alt="authenticator-icon"/> 
+                                            <img class="authenticator-logo" src="$encoder.encodeForHTML($cred.icon)" alt="authenticator-icon"/> 
                                        #end 
                                        $encoder.encodeForHTML($cred.authenticatorDescription)</td>
                                    #else
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 cf65399..4d25b8b 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
@@ -138,7 +138,7 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
                                #if ($cred.authenticatorDescription)
                                    <td>
                                    #if ($cred.icon)
-                                        <img class="authenticator-logo" src="$encoder.encodeForHTML($cred.icon) alt="authenticator-icon"/> 
+                                        <img class="authenticator-logo" src="$encoder.encodeForHTML($cred.icon)" alt="authenticator-icon"/> 
                                    #end 
                                    $encoder.encodeForHTML($cred.authenticatorDescription)</td>
                                #else
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/FidoMetadataServiceResolverFactoryTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/FidoMetadataServiceResolverFactoryTest.java
similarity index 98%
rename from webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/FidoMetadataServiceResolverFactoryTest.java
rename to webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/FidoMetadataServiceResolverFactoryTest.java
index 043c265..20f3dc1 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/FidoMetadataServiceResolverFactoryTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/FidoMetadataServiceResolverFactoryTest.java
@@ -12,7 +12,7 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.plugin.authn.webauthn.metadata;
+package net.shibboleth.idp.plugin.authn.webauthn.metadata.impl;
 
 import static org.testng.Assert.assertNotNull;
 
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/PasskeyAaguidMetadataFactoryTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/PasskeyAaguidMetadataFactoryTest.java
new file mode 100644
index 0000000..68a1a03
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/metadata/impl/PasskeyAaguidMetadataFactoryTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.metadata.impl;
+
+
+import static org.testng.Assert.assertNotNull;
+
+import org.springframework.core.io.ClassPathResource;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.shared.spring.resource.ResourceHelper;
+
+/**
+ * Tests for {@link PasskeyAaguidMetadataFactory}.
+ */
+public class PasskeyAaguidMetadataFactoryTest {
+    
+    private PasskeyAaguidMetadataFactory factory;
+    
+    @BeforeMethod
+    public void setup() {
+        factory = new PasskeyAaguidMetadataFactory();
+        factory.setId("Test passkey aaguid factory");
+    }
+    
+    @SuppressWarnings("null")
+    @Test
+    public void testCreateObject() throws Exception {        
+        factory.setPasskeyAaguidFile(ResourceHelper.of(new ClassPathResource("aaguid.json")));
+        factory.initialize();
+        
+        final PasskeyAaguidMetadataService service = factory.getObject();
+        assertNotNull(service);
+        assertNotNull(service.getEntry("ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4"));
+    }
+
+}
diff --git a/webauthn-impl/src/test/resources/aaguid.json b/webauthn-impl/src/test/resources/aaguid.json
new file mode 100644
index 0000000..6ae56de
--- /dev/null
+++ b/webauthn-impl/src/test/resources/aaguid.json
@@ -0,0 +1,130 @@
+{
+    "ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4": {
+        "name": "Google Password Manager",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDE5MiAxOTIiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDE5MiAxOTIiIHdpZHRoPSIyNHB4Ij48cmVjdCBmaWxsPSJub25lIiBoZWlnaHQ9IjE5MiIgd2lkdGg9IjE5MiIgeT0iMCIvPjxnPjxwYXRoIGQ9Ik02OS4yOSwxMDZjLTMuNDYsNS45Ny05LjkxLDEwLTE3LjI5LDEwYy0xMS4wMywwLTIwLTguOTctMjAtMjBzOC45Ny0yMCwyMC0yMCBjNy4zOCwwLDEzLjgzLDQuMDMsMTcuMjksMTBoMjUuNTVDOTAuMyw2Ni41NCw3Mi44Miw1Miw1Miw1MkMyNy43NCw1Miw4 [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDE5MiAxOTIiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDE5MiAxOTIiIHdpZHRoPSIyNHB4Ij48cmVjdCBmaWxsPSJub25lIiBoZWlnaHQ9IjE5MiIgd2lkdGg9IjE5MiIgeT0iMCIvPjxnPjxwYXRoIGQ9Ik02OS4yOSwxMDZjLTMuNDYsNS45Ny05LjkxLDEwLTE3LjI5LDEwYy0xMS4wMywwLTIwLTguOTctMjAtMjBzOC45Ny0yMCwyMC0yMCBjNy4zOCwwLDEzLjgzLDQuMDMsMTcuMjksMTBoMjUuNTVDOTAuMyw2Ni41NCw3Mi44Miw1Miw1Miw1MkMyNy43NCw1Miw [...]
+    },
+    "adce0002-35bc-c60a-648b-0b25f1f05503": {
+        "name": "Chrome on Mac",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDggNDgiPgogIDxkZWZzPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMy4yMTczIiB5MT0iMTUiIHgyPSI0NC43ODEyIiB5Mj0iMTUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZDkzMDI1Ii8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2VhNDMzNSIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxs [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDggNDgiPgogIDxkZWZzPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMy4yMTczIiB5MT0iMTUiIHgyPSI0NC43ODEyIiB5Mj0iMTUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZDkzMDI1Ii8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2VhNDMzNSIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDx [...]
+    },
+    "08987058-cadc-4b81-b6e1-30de50dcbe96": {
+        "name": "Windows Hello",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM1 [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM [...]
+    },
+    "9ddd1817-af5a-4672-a2b9-3e3dd95000a9": {
+        "name": "Windows Hello",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM1 [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM [...]
+    },
+    "6028b017-b1d4-4c02-b4b3-afcdafc96bb2": {
+        "name": "Windows Hello",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM1 [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM [...]
+    },
+    "dd4ec289-e01d-41c9-bb89-70fa845d4bf2": {
+        "name": "iCloud Keychain (Managed)",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJub25lIj48cGF0aCBkPSJtMjE3LjM2LDkwLjY5Yy0xNS41OCw5LjU0LTI1LjE3LDI2LjQxLTI1LjM4LDQ0LjY4LjA2LDIwLjY3LDEyLjQzLDM5LjMyLDMxLjQ2LDQ3LjQxLTMuNjcsMTEuODQtOS4xLDIzLjA2LTE2LjExLDMzLjI4LTEwLjAzLDE0LjQ0LTIwLjUyLDI4Ljg3LTM2LjQ3LDI4Ljg3cy0yMC4wNi05LjI3LTM4LjQ1LTkuMjctMjQuMzIsOS41Ny0zOC45LDkuNTctMjQuNzctMTMuMzctMzYuNDctMjkuNzljLTE1LjQ2LTIyLjk5LTIzLjk1LTQ5Ljk2LTI0LjQ3 [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJub25lIj48cGF0aCBkPSJtMjE3LjM2LDkwLjY5Yy0xNS41OCw5LjU0LTI1LjE3LDI2LjQxLTI1LjM4LDQ0LjY4LjA2LDIwLjY3LDEyLjQzLDM5LjMyLDMxLjQ2LDQ3LjQxLTMuNjcsMTEuODQtOS4xLDIzLjA2LTE2LjExLDMzLjI4LTEwLjAzLDE0LjQ0LTIwLjUyLDI4Ljg3LTM2LjQ3LDI4Ljg3cy0yMC4wNi05LjI3LTM4LjQ1LTkuMjctMjQuMzIsOS41Ny0zOC45LDkuNTctMjQuNzctMTMuMzctMzYuNDctMjkuNzljLTE1LjQ2LTIyLjk5LTIzLjk1LTQ5Ljk2LTI0LjQ [...]
+    },
+    "531126d6-e717-415c-9320-3d9aa6981239": {
+        "name": "Dashlane",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0yNTcuNDc0IDM1OS4yMDlDMjU3LjQ3NCAzNTYuMTg5IDI1NC40NTQgMzUzLjE2OSAyNTAuMjE1IDM1MS45NTlMMTk5LjQxMSAzMzMuMjMxQzE5MC44OTUgMzI5LjYwMSAxODEuMjY0IDMzMy44MzEgMTgxLjI2NCAzMzkuODlWNDc1Ljc3OUMxODEuMjY0IDQ3OC44MDkgMTg0LjI4MyA0ODIuNDM4IDE4Ny4zMDMgNDgzLjY0OEwyMzkuMzI2IDUwMi4zNzZDMjQ3LjE5NSA1MDUuMzk2IDI1Ny40NzQg [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0yNTcuNDc0IDM1OS4yMDlDMjU3LjQ3NCAzNTYuMTg5IDI1NC40NTQgMzUzLjE2OSAyNTAuMjE1IDM1MS45NTlMMTk5LjQxMSAzMzMuMjMxQzE5MC44OTUgMzI5LjYwMSAxODEuMjY0IDMzMy44MzEgMTgxLjI2NCAzMzkuODlWNDc1Ljc3OUMxODEuMjY0IDQ3OC44MDkgMTg0LjI4MyA0ODIuNDM4IDE4Ny4zMDMgNDgzLjY0OEwyMzkuMzI2IDUwMi4zNzZDMjQ3LjE5NSA1MDUuMzk2IDI1Ny40NzQ [...]
+    },
+    "bada5566-a7aa-401f-bd96-45619a55120d": {
+        "name": "1Password",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQwIiBoZWlnaHQ9IjI0MCIgdmlld0JveD0iMCAwIDI0MCAyNDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMjM5LjI1NyAxMjAuNDE3QzIzOS4yNTcgNTQuNDE5MyAxODUuNzU1IDAuOTE2NTA0IDExOS43NTcgMC45MTY1MDRDNTMuNzYwMSAwLjkxNjUwNCAwLjI1NzMyNCA1NC40MTkzIDAuMjU3MzI0IDEyMC40MTdDMC4yNTczMjQgMTg2LjQxNyA1My43NjAxIDIzOS45MTcgMTE5Ljc1NyAyMzkuOTE3QzE4NS43NTUgMjM5LjkxNyAy [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQwIiBoZWlnaHQ9IjI0MCIgdmlld0JveD0iMCAwIDI0MCAyNDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMjM5LjExNiAxMjAuNDE3QzIzOS4xMTYgNTQuNDE5MyAxODUuNjEzIDAuOTE2NTA0IDExOS42MTYgMC45MTY1MDRDNTMuNjE5IDAuOTE2NTA0IDAuMTE2MjExIDU0LjQxOTMgMC4xMTYyMTEgMTIwLjQxN0MwLjExNjIxMSAxODYuNDE3IDUzLjYxOSAyMzkuOTE3IDExOS42MTYgMjM5LjkxN0MxODUuNjEzIDIzOS45MTcgMjM [...]
+    },
+    "b84e4048-15dc-4dd0-8640-f4f60813c8af": {
+        "name": "NordPass",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgODAgODAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik03LjYxMzQgNzBDMi44MjQzNSA2My4zNTIgMCA1NS4xNzIyIDAgNDYuMzI3M0MwIDI0LjA1NTIgMTcuOTA4NiA2IDQwIDZDNjIuMDkxNCA2IDgwIDI0LjA1NTIgODAgNDYuMzI3M0M4MCA1NS4xNzIxIDc3LjE3NTcgNjMuMzUxOCA3Mi4zODY3IDY5Ljk5OTlMNTMuMTc0NyAzOC41NDY2TDUxLjMxOTUgNDEuNzA0Nkw1My4yMDE4IDUwLjQ4NzdMNDAgMjcuNzE0N0wzMS44 [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgODAgODAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik03LjYxMzQgNzBDMi44MjQzNSA2My4zNTIgMCA1NS4xNzIyIDAgNDYuMzI3M0MwIDI0LjA1NTIgMTcuOTA4NiA2IDQwIDZDNjIuMDkxNCA2IDgwIDI0LjA1NTIgODAgNDYuMzI3M0M4MCA1NS4xNzIxIDc3LjE3NTcgNjMuMzUxOCA3Mi4zODY3IDY5Ljk5OTlMNTMuMTc0NyAzOC41NDY2TDUxLjMxOTUgNDEuNzA0Nkw1My4yMDE4IDUwLjQ4NzdMNDAgMjcuNzE0N0wzMS4 [...]
+    },
+    "0ea242b4-43c4-4a1b-8b17-dd6d0b6baec6": {
+        "name": "Keeper",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzYwMzRfMzM2MjcpIj4KPGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTIiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMiAxMkMyMiAxNy41MjI4IDE3LjUyMjggMjIgMTIgMjJDNi40NzcxNSAyMiAyIDE3LjUyMjggMiAxMkMyIDYuNDc3MTUgNi40NzcxNSAyIDEyIDJDMTcuNTIyOCAyIDIyIDYuNDc3MTUgMjIgMTJaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNMTAu [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzYwMzRfMzM2MjcpIj4KPGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTIiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMiAxMkMyMiAxNy41MjI4IDE3LjUyMjggMjIgMTIgMjJDNi40NzcxNSAyMiAyIDE3LjUyMjggMiAxMkMyIDYuNDc3MTUgNi40NzcxNSAyIDEyIDJDMTcuNTIyOCAyIDIyIDYuNDc3MTUgMjIgMTJaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNMTA [...]
+    },
+    "891494da-2c90-4d31-a9cd-4eab0aed1309": {
+        "name": "Sésame",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cmVjdCB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiByeD0iMTc2IiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfMjAyXzMpIi8+CjxwYXRoIGQ9Ik03NjkuMTc1IDQ1Mi4xMThMMzcwLjU3OSA0NTEuNjc4QzMzOS42NTggNDUxLjY4NSAzMTAuMDA2IDQ2My45NjMgMjg4LjE0NCA0ODUuODEyQzI2Ni4yODIgNTA3LjY2MiAyNTQgNTM3LjI5NCAyNTQgNTY4LjE5Vjc5OC43MUMyNTQgODA3LjUzNyAyNTcu [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cmVjdCB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiByeD0iMTc2IiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfMjAyXzMpIi8+CjxwYXRoIGQ9Ik03NjkuMTc1IDQ1Mi4xMThMMzcwLjU3OSA0NTEuNjc4QzMzOS42NTggNDUxLjY4NSAzMTAuMDA2IDQ2My45NjMgMjg4LjE0NCA0ODUuODEyQzI2Ni4yODIgNTA3LjY2MiAyNTQgNTM3LjI5NCAyNTQgNTY4LjE5Vjc5OC43MUMyNTQgODA3LjUzNyAyNTc [...]
+    },
+    "f3809540-7f14-49c1-a8b3-8f813b225541": {
+        "name": "Enpass",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0yNTYuNDgzIDI4LjA1NTRDMzEzLjg5OSAyOC4wNTU0IDM3MS4zMTUgMjcuODg1NiA0MjguNzQ1IDI4LjE0MDVDNDQwLjY4IDI3LjkwNzYgNDUyLjUyMSAzMC4yODg5IDQ2My40NDEgMzUuMTE3OUM0NzQuMzYyIDM5Ljk0NjkgNDg0LjA5OSA0Ny4xMDczIDQ5MS45NzEgNTYuMDk4NEM1MDQuMDYyIDY5LjY5NjIgNTExLjEzMiA4Ny4wMzg3IDUxMiAxMDUuMjNDNTEyLjAyOCAxMjEuOTMzIDUxMC4w [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0yNTYuNDgzIDI4LjA1NTRDMzEzLjg5OSAyOC4wNTU0IDM3MS4zMTUgMjcuODg1NiA0MjguNzQ1IDI4LjE0MDVDNDQwLjY4IDI3LjkwNzYgNDUyLjUyMSAzMC4yODg5IDQ2My40NDEgMzUuMTE3OUM0NzQuMzYyIDM5Ljk0NjkgNDg0LjA5OSA0Ny4xMDczIDQ5MS45NzEgNTYuMDk4NEM1MDQuMDYyIDY5LjY5NjIgNTExLjEzMiA4Ny4wMzg3IDUxMiAxMDUuMjNDNTEyLjAyOCAxMjEuOTMzIDUxMC4 [...]
+    },
+    "b5397666-4885-aa6b-cebf-e52262a439a2": {
+        "name": "Chromium Browser"
+    },
+    "771b48fd-d3d4-4f74-9232-fc157ab0507a": {
+        "name": "Edge on Mac",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50KTt9LmNscy0ye29wYWNpdHk6MC4zNTtmaWxsOnVybCgjcmFkaWFsLWdyYWRpZW50KTt9LmNscy0yLC5jbHMtNHtpc29sYXRpb246aXNvbGF0ZTt9LmNscy0ze2ZpbGw6dXJsKCNsaW5lYXItZ3JhZGllbnQtMik7fS5jbHMtNHtvcGFjaXR5OjAuNDE7ZmlsbDp1cmwo [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50KTt9LmNscy0ye29wYWNpdHk6MC4zNTtmaWxsOnVybCgjcmFkaWFsLWdyYWRpZW50KTt9LmNscy0yLC5jbHMtNHtpc29sYXRpb246aXNvbGF0ZTt9LmNscy0ze2ZpbGw6dXJsKCNsaW5lYXItZ3JhZGllbnQtMik7fS5jbHMtNHtvcGFjaXR5OjAuNDE7ZmlsbDp1cmw [...]
+    },
+    "39a5647e-1853-446c-a1f6-a79bae9f5bc7": {
+        "name": "IDmelon",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2YxNWI1Yzt9LmNscy0ye2ZpbGw6IzkyMWIxZDt9LmNscy0ze2ZpbGw6I2VlMzAyNTt9LmNscy00e2ZpbGw6I2JiMjAyNjt9PC9zdHlsZT48L2RlZnM+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMzQxLjg3LDEwMC4ybC00LjI5LTEuNjRjLTMyLjMxLTExLjgxLTY1LjM2LTEzLjI3LTc2LjkyLTEzLjRsLS44OSwwSDEyMC4xMkEyMy40MywyMy40MywwLDAsMCwxMTEuNiw4N2Mt [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2YxNWI1Yzt9LmNscy0ye2ZpbGw6IzkyMWIxZDt9LmNscy0ze2ZpbGw6I2VlMzAyNTt9LmNscy00e2ZpbGw6I2JiMjAyNjt9PC9zdHlsZT48L2RlZnM+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMzQxLjg3LDEwMC4ybC00LjI5LTEuNjRjLTMyLjMxLTExLjgxLTY1LjM2LTEzLjI3LTc2LjkyLTEzLjRsLS44OSwwSDEyMC4xMkEyMy40MywyMy40MywwLDAsMCwxMTEuNiw4N2M [...]
+    },
+    "d548826e-79b4-db40-a3d8-11116f7e8349": {
+        "name": "Bitwarden",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgdmlld0JveD0iMCAwIDMwMCAzMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF82OF82MSkiPgo8cGF0aCBkPSJNMzAwIDI1My4xMjVDMzAwIDI3OS4wMjMgMjc5LjAyMyAzMDAgMjUzLjEyNSAzMDBINDYuODc1QzIwLjk3NjYgMzAwIDAgMjc5LjAyMyAwIDI1My4xMjVWNDYuODc1QzAgMjAuOTc2NiAyMC45NzY2IDAgNDYuODc1IDBIMjUzLjEyNUMyNzkuMDIzIDAgMzAwIDIwLjk3NjYgMzAwIDQ2Ljg3NVYyNTMuMTI1WiIgZmlsbD0iIzE3NUREQyIv [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgdmlld0JveD0iMCAwIDMwMCAzMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF82OF82MSkiPgo8cGF0aCBkPSJNMzAwIDI1My4xMjVDMzAwIDI3OS4wMjMgMjc5LjAyMyAzMDAgMjUzLjEyNSAzMDBINDYuODc1QzIwLjk3NjYgMzAwIDAgMjc5LjAyMyAwIDI1My4xMjVWNDYuODc1QzAgMjAuOTc2NiAyMC45NzY2IDAgNDYuODc1IDBIMjUzLjEyNUMyNzkuMDIzIDAgMzAwIDIwLjk3NjYgMzAwIDQ2Ljg3NVYyNTMuMTI1WiIgZmlsbD0iIzE3NUREQyI [...]
+    },
+    "fbfc3007-154e-4ecc-8c0b-6e020557d7bd": {
+        "name": "iCloud Keychain",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJub25lIj48cGF0aCBkPSJtMjE3LjM2LDkwLjY5Yy0xNS41OCw5LjU0LTI1LjE3LDI2LjQxLTI1LjM4LDQ0LjY4LjA2LDIwLjY3LDEyLjQzLDM5LjMyLDMxLjQ2LDQ3LjQxLTMuNjcsMTEuODQtOS4xLDIzLjA2LTE2LjExLDMzLjI4LTEwLjAzLDE0LjQ0LTIwLjUyLDI4Ljg3LTM2LjQ3LDI4Ljg3cy0yMC4wNi05LjI3LTM4LjQ1LTkuMjctMjQuMzIsOS41Ny0zOC45LDkuNTctMjQuNzctMTMuMzctMzYuNDctMjkuNzljLTE1LjQ2LTIyLjk5LTIzLjk1LTQ5Ljk2LTI0LjQ3 [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJub25lIj48cGF0aCBkPSJtMjE3LjM2LDkwLjY5Yy0xNS41OCw5LjU0LTI1LjE3LDI2LjQxLTI1LjM4LDQ0LjY4LjA2LDIwLjY3LDEyLjQzLDM5LjMyLDMxLjQ2LDQ3LjQxLTMuNjcsMTEuODQtOS4xLDIzLjA2LTE2LjExLDMzLjI4LTEwLjAzLDE0LjQ0LTIwLjUyLDI4Ljg3LTM2LjQ3LDI4Ljg3cy0yMC4wNi05LjI3LTM4LjQ1LTkuMjctMjQuMzIsOS41Ny0zOC45LDkuNTctMjQuNzctMTMuMzctMzYuNDctMjkuNzljLTE1LjQ2LTIyLjk5LTIzLjk1LTQ5Ljk2LTI0LjQ [...]
+    },
+    "53414d53-554e-4700-0000-000000000000": {
+        "name": "Samsung Pass",
+        "icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgd2lkdGg9IjUycHgiIGhlaWdodD0iNTJweCIgdmlld0JveD0iMCAwIDUyLjAgNTIuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGRlZnM+PGNsaXBQYXRoIGlkPSJpMCI+PHBhdGggZD0iTTM2MCwwIEwzNjAsODAwIEwwLDgwMCBMMCwwIEwzNjAsMCBaIj48L3BhdGg+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImkxIj48cGF0aCBkPSJNMjYsMCBDMzMuOTkxMDI3OCwwIDQxLjEzOTU4MzMs [...]
+        "icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgd2lkdGg9IjUycHgiIGhlaWdodD0iNTJweCIgdmlld0JveD0iMCAwIDUyLjAgNTIuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGRlZnM+PGNsaXBQYXRoIGlkPSJpMCI+PHBhdGggZD0iTTM2MCwwIEwzNjAsODAwIEwwLDgwMCBMMCwwIEwzNjAsMCBaIj48L3BhdGg+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImkxIj48cGF0aCBkPSJNMjYsMCBDMzMuOTkxMDI3OCwwIDQxLjEzOTU4MzM [...]
+    },
+    "66a0ccb3-bd6a-191f-ee06-e375c50b9846": {
+        "name": "Thales Bio iOS SDK",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVj [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiMyQzJGNzMiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiM1RUJGRDQiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTk [...]
+    },
+    "8836336a-f590-0921-301d-46427531eee6": {
+        "name": "Thales Bio Android SDK",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVj [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiMyQzJGNzMiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiM1RUJGRDQiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTk [...]
+    },
+    "cd69adb5-3c7a-deb9-3177-6800ea6cb72a": {
+        "name": "Thales PIN Android SDK",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVj [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiMyQzJGNzMiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiM1RUJGRDQiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTk [...]
+    },
+    "17290f1e-c212-34d0-1423-365d729f09d9": {
+        "name": "Thales PIN iOS SDK",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVj [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiMyQzJGNzMiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiM1RUJGRDQiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTk [...]
+    },
+    "50726f74-6f6e-5061-7373-50726f746f6e": {
+        "name": "Proton Pass",
+        "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMzUzLjI3NCAyMTQuMzkxQzQwOC44MzUgMTU4LjgzMiA0MzYuNjE0IDEzMS4wNTIgNDY4LjY0OCAxMjAuNjQ0QzQ5Ni44MjUgMTExLjQ4OSA1MjcuMTc1IDExMS40ODkgNTU1LjM1MiAxMjAuNjQ0QzU4Ny4zODYgMTMxLjA1MiA2MTUuMTY1IDE1OC44MzIgNjcwLjcyNiAyMTQuMzkxTDgwOS42MDggMzUzLjI3NEM4NjUuMTY5IDQwOC44MzUgODkyLjk0OCA0MzYuNjE0IDkwMy4zNTYgNDY4 [...]
+        "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMzUzLjI3NCAyMTQuMzkxQzQwOC44MzUgMTU4LjgzMiA0MzYuNjE0IDEzMS4wNTIgNDY4LjY0OCAxMjAuNjQ0QzQ5Ni44MjUgMTExLjQ4OSA1MjcuMTc1IDExMS40ODkgNTU1LjM1MiAxMjAuNjQ0QzU4Ny4zODYgMTMxLjA1MiA2MTUuMTY1IDE1OC44MzIgNjcwLjcyNiAyMTQuMzkxTDgwOS42MDggMzUzLjI3NEM4NjUuMTY5IDQwOC44MzUgODkyLjk0OCA0MzYuNjE0IDkwMy4zNTYgNDY [...]
+    },
+    "fdb141b2-5d84-443e-8a35-4698c205a502": {
+        "name": "KeePassXC",
+        "icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIGlkPSJiIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48ZyBpZD0iYyI+PHJlY3Qgd2lkdGg9IjEyOCIgaGVpZ2h0PSIxMjgiIHN0eWxlPSJmaWxsOm5vbmU7IHN0cm9rZS13aWR0aDowcHg7Ii8+PGcgaWQ9ImQiPjxyZWN0IHg9IjU3LjAxMTIiIHk9IjU2LjU3NzEiIHdpZHRoPSIzLjU1OCIgaGVpZ2h0PSIzNS4yMDYxIiBzdHlsZT0iZmlsbDojMGYwZjBkOyBzdHJva2Utd2lkdGg6MHB4OyIvPjxwYXRoIGQ9Im02NCwyOC40ODg1YzQuODI4NywwLDguNzY3 [...]
+        "icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImUiIHgxPSI2My45OTk4IiB5MT0iLTMuMzE0IiB4Mj0iNjMuOTk5OCIgeTI9IjExOC40ODYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM0MTQxNDEiLz48c3RvcCBvZmZzZXQ9Ii4xOTYiIHN0b3AtY29sb3I9IiMzZTNlM2U [...]
+    },
+    "cc45f64e-52a2-451b-831a-4edd8022a202": {
+        "name": "ToothPic Passkey Provider",
+        "icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgdmlld0JveD0iMCAwIDY2Ni42NjY2OSA2NjYuNjY2NjkiCiAgIGhlaWdodD0iNjY2LjY2NjY5IgogICB3aWR0aD0iNjY2LjY2NjY5IgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBpZD0ic3ZnMiIKICAgdmVyc2lvbj0iMS4xIgogICBzb2RpcG9kaTpkb2NuYW1lPSJsb2dvUXVhZHJhdG9fMDMuc3ZnIgogICBpbmtzY2FwZTpleHBvcnQtZmlsZW5hbWU9ImxvZ29RdWFkcmF0b19mb25kb190cmFzcGFyZW50ZS5zdmciCiAgIGlua3NjYXBlOmV4cG9ydC14ZHBpPSI0ODAuNTQi [...]
+        "icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmlld0JveD0iMCAwIDY2Ni42NjY2OSA2NjYuNjY2NjkiCiAgIGh [...]
+    },
+    "bfc748bb-3429-4faa-b9f9-7cfa9f3b76d0":{
+        "name": "iPasswords",
+        "icon_dark":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwODAgMTA4MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxuczpzZXJpZ [...]
+        "icon_light":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwODAgMTA4MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxuczpzZXJp [...]
+    }
+}

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


More information about the commits mailing list