From 0e7561bd611ae02d9ac4a62457b460816d70eed4 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 30 Dec 2023 18:53:01 +0100 Subject: [PATCH 1/5] Move daemon and ssh socket paths --- agent/actions/config.go | 9 +++++++++ agent/config/config.go | 2 ++ agent/ssh/ssh.go | 16 ++++++---------- agent/unixsocketagent.go | 2 +- browserbiometrics/main.go | 5 +++-- browserbiometrics/protocol.go | 8 ++++++-- client/unixsocketclient.go | 16 +++++++--------- cmd/config.go | 29 +++++++++++++++++++++++++++++ cmd/daemonize.go | 16 +--------------- cmd/root.go | 2 +- ipc/messages/config.go | 26 ++++++++++++++++++++++++++ main.go | 34 +++++++++++++++++++++++++++++----- ui/goldwarden.py | 11 +++++++++++ ui/settings.py | 15 ++++++++++++--- 14 files changed, 143 insertions(+), 48 deletions(-) diff --git a/agent/actions/config.go b/agent/actions/config.go index eb066b9..673af52 100644 --- a/agent/actions/config.go +++ b/agent/actions/config.go @@ -55,8 +55,17 @@ func handleSetNotifications(request messages.IPCMessage, cfg *config.Config, vau }) } +func handleGetRuntimeConfig(request messages.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (response messages.IPCMessage, err error) { + return messages.IPCMessageFromPayload(messages.GetRuntimeConfigResponse{ + UseMemguard: cfg.ConfigFile.RuntimeConfig.UseMemguard, + SSHAgentSocketPath: cfg.ConfigFile.RuntimeConfig.SSHAgentSocketPath, + GoldwardenSocketPath: cfg.ConfigFile.RuntimeConfig.GoldwardenSocketPath, + }) +} + func init() { AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.SetIdentityURLRequest{}), handleSetIdentity) AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.SetApiURLRequest{}), handleSetApiURL) AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.SetNotificationsURLRequest{}), handleSetNotifications) + AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.GetRuntimeConfigRequest{}), handleGetRuntimeConfig) } diff --git a/agent/config/config.go b/agent/config/config.go index ff0de05..6b1eb00 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -46,6 +46,8 @@ type RuntimeConfig struct { Password string Pin string UseMemguard bool + SSHAgentSocketPath string + GoldwardenSocketPath string } type ConfigFile struct { diff --git a/agent/ssh/ssh.go b/agent/ssh/ssh.go index f7edf02..512f490 100644 --- a/agent/ssh/ssh.go +++ b/agent/ssh/ssh.go @@ -144,6 +144,7 @@ func (vaultAgent) Unlock(passphrase []byte) error { type SSHAgentServer struct { vault *vault.Vault config *config.Config + runtimeConfig *config.RuntimeConfig unlockRequestAction func() bool } @@ -151,10 +152,11 @@ func (v *SSHAgentServer) SetUnlockRequestAction(action func() bool) { v.unlockRequestAction = action } -func NewVaultAgent(vault *vault.Vault, config *config.Config) SSHAgentServer { +func NewVaultAgent(vault *vault.Vault, config *config.Config, runtimeConfig *config.RuntimeConfig) SSHAgentServer { return SSHAgentServer{ - vault: vault, - config: config, + vault: vault, + config: config, + runtimeConfig: runtimeConfig, unlockRequestAction: func() bool { log.Info("Unlock Request, but no action defined") return false @@ -163,13 +165,7 @@ func NewVaultAgent(vault *vault.Vault, config *config.Config) SSHAgentServer { } func (v SSHAgentServer) Serve() { - home, err := os.UserHomeDir() - if err != nil { - panic(err) - } - - path := home + "/.goldwarden-ssh-agent.sock" - + path := v.runtimeConfig.SSHAgentSocketPath if _, err := os.Stat(path); err == nil { if err := os.Remove(path); err != nil { log.Error("Could not remove old socket file: %s", err) diff --git a/agent/unixsocketagent.go b/agent/unixsocketagent.go index 292271c..8bc375d 100644 --- a/agent/unixsocketagent.go +++ b/agent/unixsocketagent.go @@ -203,7 +203,7 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error { }() if !runtimeConfig.DisableSSHAgent { - vaultAgent := ssh.NewVaultAgent(vault, &cfg) + vaultAgent := ssh.NewVaultAgent(vault, &cfg, &runtimeConfig) vaultAgent.SetUnlockRequestAction(func() bool { err := cfg.TryUnlock(vault) if err == nil { diff --git a/browserbiometrics/main.go b/browserbiometrics/main.go index be3617e..3f8829a 100644 --- a/browserbiometrics/main.go +++ b/browserbiometrics/main.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/browserbiometrics/logging" ) @@ -13,7 +14,7 @@ const appID = "com.quexten.bw-bio-handler" var transportKey []byte -func Main() { +func Main(rtCfg *config.RuntimeConfig) { if os.Args[1] == "install" { var err error err = detectAndInstallBrowsers(".config") @@ -32,7 +33,7 @@ func Main() { logging.Debugf("Generated transport key") setupCommunication() - readLoop() + readLoop(rtCfg) } func DetectAndInstallBrowsers() error { diff --git a/browserbiometrics/protocol.go b/browserbiometrics/protocol.go index ecf97b0..d8ed9cc 100644 --- a/browserbiometrics/protocol.go +++ b/browserbiometrics/protocol.go @@ -6,12 +6,16 @@ import ( "io" "os" + "github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/browserbiometrics/logging" "github.com/quexten/goldwarden/client" "github.com/quexten/goldwarden/ipc/messages" ) -func readLoop() { +var runtimeConfig *config.RuntimeConfig + +func readLoop(rtCfg *config.RuntimeConfig) { + runtimeConfig = rtCfg v := bufio.NewReader(os.Stdin) s := bufio.NewReaderSize(v, bufferSize) @@ -101,7 +105,7 @@ func handlePayloadMessage(msg PayloadMessage, appID string) { case "biometricUnlock": logging.Debugf("Biometric unlock requested") // logging.Debugf("Biometrics authorized: %t", isAuthorized) - result, err := client.NewUnixSocketClient().SendToAgent(messages.GetBiometricsKeyRequest{}) + result, err := client.NewUnixSocketClient(runtimeConfig).SendToAgent(messages.GetBiometricsKeyRequest{}) if err != nil { logging.Errorf("Unable to send message to agent: %s", err.Error()) return diff --git a/client/unixsocketclient.go b/client/unixsocketclient.go index d71f5d9..f24be0d 100644 --- a/client/unixsocketclient.go +++ b/client/unixsocketclient.go @@ -5,18 +5,21 @@ import ( "io" "log" "net" - "os" + "github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/ipc/messages" ) const READ_BUFFER = 1 * 1024 * 1024 // 1MB type UnixSocketClient struct { + runtimeConfig *config.RuntimeConfig } -func NewUnixSocketClient() UnixSocketClient { - return UnixSocketClient{} +func NewUnixSocketClient(runtimeConfig *config.RuntimeConfig) UnixSocketClient { + return UnixSocketClient{ + runtimeConfig: runtimeConfig, + } } func reader(r io.Reader) interface{} { @@ -37,12 +40,7 @@ func reader(r io.Reader) interface{} { } func (client UnixSocketClient) SendToAgent(request interface{}) (interface{}, error) { - home, err := os.UserHomeDir() - if err != nil { - panic(err) - } - - c, err := net.Dial("unix", home+"/.goldwarden.sock") + c, err := net.Dial("unix", client.runtimeConfig.GoldwardenSocketPath) if err != nil { return nil, err } diff --git a/cmd/config.go b/cmd/config.go index 2f09d68..c4652a5 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/quexten/goldwarden/ipc/messages" "github.com/spf13/cobra" ) @@ -104,6 +106,32 @@ var setNotificationsURLCmd = &cobra.Command{ }, } +var getRuntimeConfigCmd = &cobra.Command{ + Use: "get-runtime-config", + Short: "Get the runtime config", + Long: `Get the runtime config.`, + Run: func(cmd *cobra.Command, args []string) { + request := messages.GetRuntimeConfigRequest{} + + result, err := commandClient.SendToAgent(request) + if err != nil { + handleSendToAgentError(err) + return + } + + switch result := result.(type) { + case messages.GetRuntimeConfigResponse: + fmt.Println("{") + fmt.Println(" \"useMemguard\": " + fmt.Sprintf("%t", result.UseMemguard) + ",") + fmt.Println(" \"SSHAgentSocketPath\": \"" + result.SSHAgentSocketPath + "\",") + fmt.Println(" \"goldwardenSocketPath\": \"" + result.GoldwardenSocketPath + "\"") + fmt.Println("}") + default: + println("Wrong IPC response type") + } + }, +} + var configCmd = &cobra.Command{ Use: "config", Short: "Manage the configuration", @@ -115,4 +143,5 @@ func init() { configCmd.AddCommand(setApiUrlCmd) configCmd.AddCommand(setIdentityURLCmd) configCmd.AddCommand(setNotificationsURLCmd) + configCmd.AddCommand(getRuntimeConfigCmd) } diff --git a/cmd/daemonize.go b/cmd/daemonize.go index 24909cd..c2fef56 100644 --- a/cmd/daemonize.go +++ b/cmd/daemonize.go @@ -3,7 +3,6 @@ package cmd import ( "os" "os/signal" - "strings" "github.com/awnumar/memguard" "github.com/quexten/goldwarden/agent" @@ -19,15 +18,6 @@ var daemonizeCmd = &cobra.Command{ websocketDisabled := runtimeConfig.WebsocketDisabled sshDisabled := runtimeConfig.DisableSSHAgent - _, err := os.Stat("/.flatpak-info") - isFlatpak := err == nil - if isFlatpak { - runtimeConfig.ConfigDirectory = "~/.var/app/com.quexten.Goldwarden/config/goldwarden.json" - userHome, _ := os.UserHomeDir() - runtimeConfig.ConfigDirectory = strings.ReplaceAll(runtimeConfig.ConfigDirectory, "~", userHome) - println("Flatpak Config directory: " + runtimeConfig.ConfigDirectory) - } - if websocketDisabled { println("Websocket disabled") } @@ -42,11 +32,7 @@ var daemonizeCmd = &cobra.Command{ <-signalChannel memguard.SafeExit(0) }() - home, err := os.UserHomeDir() - if err != nil { - panic(err) - } - err = agent.StartUnixAgent(home+"/.goldwarden.sock", runtimeConfig) + err := agent.StartUnixAgent(runtimeConfig.GoldwardenSocketPath, runtimeConfig) if err != nil { panic(err) } diff --git a/cmd/root.go b/cmd/root.go index 83aab7e..f8e2931 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,7 +29,7 @@ func Execute(cfg config.RuntimeConfig) { recv, send := agent.StartVirtualAgent(runtimeConfig) commandClient = client.NewVirtualClient(send, recv) } else { - commandClient = client.NewUnixSocketClient() + commandClient = client.NewUnixSocketClient(&cfg) } err := rootCmd.Execute() diff --git a/ipc/messages/config.go b/ipc/messages/config.go index cc2b90d..41ed12a 100644 --- a/ipc/messages/config.go +++ b/ipc/messages/config.go @@ -14,6 +14,14 @@ type SetNotificationsURLRequest struct { Value string } +type GetRuntimeConfigRequest struct{} + +type GetRuntimeConfigResponse struct { + UseMemguard bool + SSHAgentSocketPath string + GoldwardenSocketPath string +} + func init() { registerPayloadParser(func(payload []byte) (interface{}, error) { var req SetApiURLRequest @@ -41,4 +49,22 @@ func init() { } return req, nil }, SetNotificationsURLRequest{}) + + registerPayloadParser(func(payload []byte) (interface{}, error) { + var req GetRuntimeConfigRequest + err := json.Unmarshal(payload, &req) + if err != nil { + panic("Unmarshal: " + err.Error()) + } + return req, nil + }, GetRuntimeConfigRequest{}) + + registerPayloadParser(func(payload []byte) (interface{}, error) { + var req GetRuntimeConfigResponse + err := json.Unmarshal(payload, &req) + if err != nil { + panic("Unmarshal: " + err.Error()) + } + return req, nil + }, GetRuntimeConfigResponse{}) } diff --git a/main.go b/main.go index 7093703..8c222a9 100644 --- a/main.go +++ b/main.go @@ -10,11 +10,6 @@ import ( ) func main() { - if len(os.Args) > 1 && (strings.Contains(os.Args[1], "com.8bit.bitwarden.json") || strings.Contains(os.Args[1], "chrome-extension://")) { - browserbiometrics.Main() - return - } - var configPath string if path, found := os.LookupEnv("GOLDWARDEN_CONFIG_DIRECTORY"); found { configPath = path @@ -39,10 +34,39 @@ func main() { Password: os.Getenv("GOLDWARDEN_AUTH_PASSWORD"), Pin: os.Getenv("GOLDWARDEN_PIN"), UseMemguard: os.Getenv("GOLDWARDEN_NO_MEMGUARD") != "true", + SSHAgentSocketPath: os.Getenv("GOLDWARDEN_SSH_AUTH_SOCK"), + GoldwardenSocketPath: os.Getenv("GOLDWARDEN_SOCKET_PATH"), ConfigDirectory: configPath, } + if len(os.Args) > 1 && (strings.Contains(os.Args[1], "com.8bit.bitwarden.json") || strings.Contains(os.Args[1], "chrome-extension://")) { + browserbiometrics.Main(&runtimeConfig) + return + } + + home, err := os.UserHomeDir() + if err != nil { + panic(err) + } + if runtimeConfig.SSHAgentSocketPath == "" { + runtimeConfig.SSHAgentSocketPath = home + "/.goldwarden-ssh-agent.sock" + } + if runtimeConfig.GoldwardenSocketPath == "" { + runtimeConfig.GoldwardenSocketPath = home + "/.goldwarden.sock" + } + + _, err = os.Stat("/.flatpak-info") + isFlatpak := err == nil + if isFlatpak { + userHome, _ := os.UserHomeDir() + runtimeConfig.ConfigDirectory = userHome + "/.var/app/com.quexten.Goldwarden/config/goldwarden.json" + runtimeConfig.ConfigDirectory = strings.ReplaceAll(runtimeConfig.ConfigDirectory, "~", userHome) + println("Flatpak Config directory: " + runtimeConfig.ConfigDirectory) + runtimeConfig.SSHAgentSocketPath = userHome + "/.var/app/com.quexten.Goldwarden/data/ssh-auth-sock" + runtimeConfig.GoldwardenSocketPath = userHome + "/.var/app/com.quexten.Goldwarden/data/goldwarden.sock" + } + if runtimeConfig.SingleProcess { runtimeConfig.DisablePinRequirement = true runtimeConfig.DisableAuth = true diff --git a/ui/goldwarden.py b/ui/goldwarden.py index b1dc9a5..b78c97b 100644 --- a/ui/goldwarden.py +++ b/ui/goldwarden.py @@ -103,6 +103,17 @@ def get_vault_logins(): except Exception as e: print(e) return None + +def get_runtime_config(): + restic_cmd = f"{BINARY_PATH} config get-runtime-config" + result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) + if result.returncode != 0: + return None + try: + return json.loads(result.stdout) + except Exception as e: + print(e) + return None def autotype(username, password): # environment diff --git a/ui/settings.py b/ui/settings.py index 389c2d0..0719505 100644 --- a/ui/settings.py +++ b/ui/settings.py @@ -30,11 +30,15 @@ class SettingsWinvdow(Gtk.ApplicationWindow): self.ssh_row = Adw.ActionRow() self.ssh_row.set_title("SSH Daemon") - self.ssh_row.set_subtitle("Listening at ~/.goldwarden-ssh-agent.sock") + self.ssh_row.set_subtitle("Getting status...") + self.ssh_row.set_icon_name("emblem-default") self.preferences_group.add(self.ssh_row) - self.icon = components.status_icon_ok("emblem-default") - self.ssh_row.add_prefix(self.icon) + self.goldwarden_daemon_row = Adw.ActionRow() + self.goldwarden_daemon_row.set_title("Goldwarden Daemon") + self.goldwarden_daemon_row.set_subtitle("Getting status...") + self.goldwarden_daemon_row.set_icon_name("emblem-default") + self.preferences_group.add(self.goldwarden_daemon_row) self.login_with_device = Adw.ActionRow() self.login_with_device.set_title("Login with device") @@ -168,6 +172,11 @@ class SettingsWinvdow(Gtk.ApplicationWindow): pin_set = goldwarden.is_pin_enabled() status = goldwarden.get_vault_status() + runtimeCfg = goldwarden.get_runtime_config() + if runtimeCfg != None: + self.ssh_row.set_subtitle("Listening at "+runtimeCfg["SSHAgentSocketPath"]) + self.goldwarden_daemon_row.set_subtitle("Listening at "+runtimeCfg["goldwardenSocketPath"]) + if status != None: if pin_set: self.unlock_button.set_sensitive(True) From 94c26a76dc7bcf9615813f1e4302fd66f320d137 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 30 Dec 2023 19:24:58 +0100 Subject: [PATCH 2/5] Add copy notification --- ui/autofill.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/autofill.py b/ui/autofill.py index 9c05f2b..dbe1489 100644 --- a/ui/autofill.py +++ b/ui/autofill.py @@ -3,12 +3,13 @@ gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') import gc import time -from gi.repository import Gtk, Adw, GLib +from gi.repository import Gtk, Adw, GLib, Notify import goldwarden import clipboard from threading import Thread import sys import os +Notify.init("Goldwarden") class MyApp(Adw.Application): def __init__(self, **kwargs): @@ -93,9 +94,13 @@ class MainWindow(Gtk.ApplicationWindow): if keyval == 112: print("copy password") clipboard.write(self.history_list.get_selected_row().password) + Notify.Notification.new("Goldwarden", "Password Copied", "dialog-information").show() elif keyval == 117: print("copy username") clipboard.write(self.history_list.get_selected_row().username) + notification=Notify.Notification.new("Goldwarden", "Username Copied", "dialog-information") + notification.set_timeout(5) + notification.show() keycont.connect('key-pressed', handle_keypress, self) self.add_controller(keycont) From 81298aa7269691b465e58dda01d8b3d6544f5e1a Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 30 Dec 2023 19:53:20 +0100 Subject: [PATCH 3/5] Fix 'never' sync not being detected in some timezones --- ui/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/settings.py b/ui/settings.py index 0719505..bc45e43 100644 --- a/ui/settings.py +++ b/ui/settings.py @@ -219,7 +219,7 @@ class SettingsWinvdow(Gtk.ApplicationWindow): else: self.websocket_connected_status_icon.set_icon("dialog-error", "error") self.last_sync_row.set_subtitle(str(status["lastSynced"])) - if status["lastSynced"].startswith("1970"): + if status["lastSynced"].startswith("1970") or status["lastSynced"].startswith("1969"): self.last_sync_row.set_subtitle("Never") self.unlock_button.set_label("Unlock" if locked else "Lock") else: From 13834283f12305684b316102318f77d3734c99f2 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 30 Dec 2023 21:00:36 +0100 Subject: [PATCH 4/5] Improve browserbiometrics setup --- browserbiometrics/main.go | 75 ++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/browserbiometrics/main.go b/browserbiometrics/main.go index 3f8829a..1535726 100644 --- a/browserbiometrics/main.go +++ b/browserbiometrics/main.go @@ -10,24 +10,23 @@ import ( "github.com/quexten/goldwarden/browserbiometrics/logging" ) +var chromiumPaths = []string{ + "~/.config/google-chrome/", + "~/.config/google-chrome-beta/", + "~/.config/google-chrome-unstable/", + "~/.config/chromium/", + "~/.config/BraveSoftware/Brave-Browser/", + "~/.config/thorium/", + "~/.config/microsoft-edge-beta/", + "~/.config/microsoft-edge-dev/", +} +var mozillaPaths = []string{"~/.mozilla/", "~/.librewolf/", "~/.waterfox/"} + const appID = "com.quexten.bw-bio-handler" var transportKey []byte func Main(rtCfg *config.RuntimeConfig) { - if os.Args[1] == "install" { - var err error - err = detectAndInstallBrowsers(".config") - if err != nil { - panic("Failed to detect browsers: " + err.Error()) - } - err = detectAndInstallBrowsers(".mozilla") - if err != nil { - panic("Failed to detect browsers: " + err.Error()) - } - return - } - logging.Debugf("Starting browserbiometrics") transportKey = generateTransportKey() logging.Debugf("Generated transport key") @@ -38,13 +37,57 @@ func Main(rtCfg *config.RuntimeConfig) { func DetectAndInstallBrowsers() error { var err error + + // first, ensure the native messaging hosts dirs exist + for _, path := range chromiumPaths { + path = strings.ReplaceAll(path, "~", os.Getenv("HOME")) + _, err = os.Stat(path) + if err != nil { + continue + } + + _, err = os.Stat(path + "NativeMessagingHosts/") + if err == nil { + fmt.Println("Native messaging host directory already exists: " + path + "NativeMessagingHosts/") + continue + } + err = os.MkdirAll(path+"NativeMessagingHosts/", 0755) + if err != nil { + fmt.Println("Error creating native messaging host directory: " + err.Error()) + } else { + fmt.Println("Created native messaging host directory: " + path + "NativeMessagingHosts/") + } + } + for _, path := range mozillaPaths { + path = strings.ReplaceAll(path, "~", os.Getenv("HOME")) + _, err = os.Stat(path) + if err != nil { + continue + } + + _, err = os.Stat(path + "native-messaging-hosts/") + if err == nil { + fmt.Println("Native messaging host directory already exists: " + path + "native-messaging-hosts/") + continue + } + err = os.MkdirAll(path+"native-messaging-hosts/", 0755) + if err != nil { + fmt.Println("Error creating native messaging host directory: " + err.Error()) + } else { + fmt.Println("Created native messaging host directory: " + path + "native-messaging-hosts/") + } + } + err = detectAndInstallBrowsers(".config") if err != nil { return err } - err = detectAndInstallBrowsers(".mozilla") - if err != nil { - return err + for _, path := range mozillaPaths { + path = strings.ReplaceAll(path, "~/", "") + err = detectAndInstallBrowsers(path) + if err != nil { + return err + } } return nil } From b5337f6af60116b13390cf2edf27e9cf9bb562bd Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 30 Dec 2023 21:04:28 +0100 Subject: [PATCH 5/5] Update PKGBUILD --- PKGBUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PKGBUILD b/PKGBUILD index ae487c6..c2ea3a2 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,8 +1,8 @@ pkgname=goldwarden -pkgver=0.2.6 +pkgver=0.2.7 pkgrel=1 pkgdesc='A feature-packed Bitwarden compatible desktop integration' -arch=('x86_64') +arch=('x86_64', 'aarch64') url="https://github.com/quexten/$pkgname" license=('MIT') depends=('libfido2')