From cf6221c080199a8deab3976de42fad7a010318be Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 8 Feb 2024 16:35:07 +0100 Subject: [PATCH] Add initial authenticated connection work --- .gitignore | 3 +- agent/bitwarden/http.go | 3 +- agent/config/config.go | 1 + agent/sockets/callingcontext.go | 2 + agent/systemauth/systemauth.go | 4 + agent/unixsocketagent.go | 43 +++- cmd/autofill.go | 7 +- cmd/config.go | 71 +++---- cmd/daemonize.go | 4 +- cmd/login.go | 10 +- cmd/logins.go | 2 +- cmd/pin.go | 12 +- cmd/root.go | 5 +- cmd/run.go | 5 +- cmd/send.go | 2 +- cmd/session.go | 60 ++++++ cmd/ssh.go | 4 +- cmd/vault.go | 40 ++-- ipc/messages/session.go | 31 +++ main.go | 4 +- ui/goldwarden.py | 184 ------------------ ui/main.py | 53 ----- ui/src/goldwarden_ui_main.py | 3 + ui/{ => src/linux}/autotype/autotype.py | 0 ui/{ => src/linux}/background.py | 0 ui/{ => src/linux}/clipboard.py | 0 ui/src/linux/main.py | 59 ++++++ .../linux}/monitors/dbus_autofill_monitor.py | 7 +- ui/src/linux/monitors/dbus_monitor.py | 41 ++++ .../linux}/monitors/global_shortcut_portal.py | 0 ui/src/services/goldwarden.py | 159 +++++++++++++++ ui/{ => src/services}/totp.py | 0 ui/{ => src/ui}/components.py | 0 ui/{autofill.py => src/ui/quickaccess.py} | 15 +- ui/{ => src/ui}/settings.py | 7 +- 35 files changed, 513 insertions(+), 328 deletions(-) create mode 100644 cmd/session.go create mode 100644 ipc/messages/session.go delete mode 100644 ui/goldwarden.py delete mode 100644 ui/main.py create mode 100644 ui/src/goldwarden_ui_main.py rename ui/{ => src/linux}/autotype/autotype.py (100%) rename ui/{ => src/linux}/background.py (100%) rename ui/{ => src/linux}/clipboard.py (100%) create mode 100644 ui/src/linux/main.py rename ui/{ => src/linux}/monitors/dbus_autofill_monitor.py (91%) create mode 100644 ui/src/linux/monitors/dbus_monitor.py rename ui/{ => src/linux}/monitors/global_shortcut_portal.py (100%) create mode 100644 ui/src/services/goldwarden.py rename ui/{ => src/services}/totp.py (100%) rename ui/{ => src/ui}/components.py (100%) rename ui/{autofill.py => src/ui/quickaccess.py} (94%) rename ui/{ => src/ui}/settings.py (99%) diff --git a/.gitignore b/.gitignore index 56c7c8a..bab7d6d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ goldwarden __pycache__ .flatpak-builder flatpak-pip-generator -repo \ No newline at end of file +repo +__debug* \ No newline at end of file diff --git a/agent/bitwarden/http.go b/agent/bitwarden/http.go index 610879f..2e6d1a7 100644 --- a/agent/bitwarden/http.go +++ b/agent/bitwarden/http.go @@ -10,7 +10,6 @@ import ( "io/ioutil" "net/http" "net/url" - "os" "strings" "time" ) @@ -114,7 +113,7 @@ func makeAuthenticatedHTTPRequest(ctx context.Context, req *http.Request, recv i return &errStatusCode{res.StatusCode, body} } if err := json.Unmarshal(body, recv); err != nil { - fmt.Fprintln(os.Stderr, string(body)) + fmt.Println(string(body)) return err } return nil diff --git a/agent/config/config.go b/agent/config/config.go index ac81136..9b5a77e 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -49,6 +49,7 @@ type RuntimeConfig struct { UseMemguard bool SSHAgentSocketPath string GoldwardenSocketPath string + DaemonAuthToken string } type ConfigFile struct { diff --git a/agent/sockets/callingcontext.go b/agent/sockets/callingcontext.go index 7453446..7516be7 100644 --- a/agent/sockets/callingcontext.go +++ b/agent/sockets/callingcontext.go @@ -17,6 +17,7 @@ type CallingContext struct { ParentProcessPid int GrandParentProcessPid int Error bool + Authenticated bool } func GetCallingContext(connection net.Conn) CallingContext { @@ -30,6 +31,7 @@ func GetCallingContext(connection net.Conn) CallingContext { ParentProcessPid: 0, GrandParentProcessPid: 0, Error: true, + Authenticated: false, } if err != nil { return errorContext diff --git a/agent/systemauth/systemauth.go b/agent/systemauth/systemauth.go index 78e2995..c3655ac 100644 --- a/agent/systemauth/systemauth.go +++ b/agent/systemauth/systemauth.go @@ -65,6 +65,10 @@ func (s *SessionStore) verifySession(ctx sockets.CallingContext, sessionType Ses // with session func GetPermission(sessionType SessionType, ctx sockets.CallingContext, config *config.Config) (bool, error) { + if ctx.Authenticated { + return true, nil + } + log.Info("Checking permission for " + ctx.ProcessName + " with session type " + string(sessionType)) var actionDescription = "" biometricsApprovalType := biometrics.AccessVault diff --git a/agent/unixsocketagent.go b/agent/unixsocketagent.go index 101007d..b7eff30 100644 --- a/agent/unixsocketagent.go +++ b/agent/unixsocketagent.go @@ -16,6 +16,7 @@ import ( "github.com/quexten/goldwarden/agent/processsecurity" "github.com/quexten/goldwarden/agent/sockets" "github.com/quexten/goldwarden/agent/ssh" + "github.com/quexten/goldwarden/agent/systemauth" "github.com/quexten/goldwarden/agent/vault" "github.com/quexten/goldwarden/ipc/messages" "github.com/quexten/goldwarden/logging" @@ -44,7 +45,7 @@ func writeError(c net.Conn, errMsg error) error { return nil } -func serveAgentSession(c net.Conn, ctx context.Context, vault *vault.Vault, cfg *config.Config) { +func serveAgentSession(c net.Conn, vault *vault.Vault, cfg *config.Config) { for { buf := make([]byte, 1024*1024) nr, err := c.Read(buf) @@ -61,7 +62,38 @@ func serveAgentSession(c net.Conn, ctx context.Context, vault *vault.Vault, cfg continue } - responseBytes := []byte{} + if msg.Type == messages.MessageTypeForEmptyPayload(messages.SessionAuthRequest{}) { + log.Info("Received session auth request") + req := messages.ParsePayload(msg).(messages.SessionAuthRequest) + fmt.Println("Daemontoken", cfg.ConfigFile.RuntimeConfig.DaemonAuthToken) + fmt.Println("Token", req.Token) + fmt.Println(len(cfg.ConfigFile.RuntimeConfig.DaemonAuthToken), len(req.Token)) + payload := messages.SessionAuthResponse{ + Verified: cfg.ConfigFile.RuntimeConfig.DaemonAuthToken == req.Token, + } + log.Info("Verified: %t", payload.Verified) + callingContext := sockets.GetCallingContext(c) + systemauth.CreatePinSession(callingContext) + + responsePayload, err := messages.IPCMessageFromPayload(payload) + if err != nil { + writeError(c, err) + continue + } + payloadBytes, err := json.Marshal(responsePayload) + if err != nil { + writeError(c, err) + continue + } + + _, err = c.Write(payloadBytes) + if err != nil { + log.Error("Failed writing to socket " + err.Error()) + } + continue + } + + var responseBytes []byte if action, actionFound := actions.AgentActionsRegistry.Get(msg.Type); actionFound { callingContext := sockets.GetCallingContext(c) payload, err := action(msg, cfg, vault, &callingContext) @@ -291,7 +323,7 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error { l, err := net.Listen("unix", path) if err != nil { - println("listen error", err.Error()) + fmt.Println("listen error", err.Error()) return err } defer l.Close() @@ -301,10 +333,11 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error { for { fd, err := l.Accept() if err != nil { - println("accept error", err.Error()) + fmt.Println("accept error", err.Error()) } + fmt.Println("Accepted connection") - go serveAgentSession(fd, ctx, vault, &cfg) + go serveAgentSession(fd, vault, &cfg) } }() diff --git a/cmd/autofill.go b/cmd/autofill.go index c569e70..cf33b9d 100644 --- a/cmd/autofill.go +++ b/cmd/autofill.go @@ -10,9 +10,10 @@ import ( ) var autofillCmd = &cobra.Command{ - Use: "autotype", - Short: "Autotype credentials", - Long: `Autotype credentials`, + Hidden: true, + Use: "autotype", + Short: "Autotype credentials", + Long: `Autotype credentials`, Run: func(cmd *cobra.Command, args []string) { username, _ := cmd.Flags().GetString("username") // get pasword from env diff --git a/cmd/config.go b/cmd/config.go index 01812b0..a54ed02 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "strings" @@ -30,12 +31,12 @@ var setApiUrlCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Done") + fmt.Println("Done") } else { - println("Setting api url failed: " + result.(messages.ActionResponse).Message) + fmt.Println("Setting api url failed: " + result.(messages.ActionResponse).Message) } default: - println("Wrong IPC response type") + fmt.Println("Wrong IPC response type") } }, @@ -63,12 +64,12 @@ var setIdentityURLCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Done") + fmt.Println("Done") } else { - println("Setting identity url failed: " + result.(messages.ActionResponse).Message) + fmt.Println("Setting identity url failed: " + result.(messages.ActionResponse).Message) } default: - println("Wrong IPC response type") + fmt.Println("Wrong IPC response type") } }, @@ -96,12 +97,12 @@ var setNotificationsURLCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Done") + fmt.Println("Done") } else { - println("Setting notifications url failed: " + result.(messages.ActionResponse).Message) + fmt.Println("Setting notifications url failed: " + result.(messages.ActionResponse).Message) } default: - println("Wrong IPC response type") + fmt.Println("Wrong IPC response type") } }, @@ -129,12 +130,12 @@ var setVaultURLCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Done") + fmt.Println("Done") } else { - println("Setting vault url failed: " + result.(messages.ActionResponse).Message) + fmt.Println("Setting vault url failed: " + result.(messages.ActionResponse).Message) } default: - println("Wrong IPC response type") + fmt.Println("Wrong IPC response type") } }, @@ -162,12 +163,12 @@ var setURLsAutomaticallyCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Done") + fmt.Println("Done") } else { - println("Setting urls automatically failed: " + result.(messages.ActionResponse).Message) + fmt.Println("Setting urls automatically failed: " + result.(messages.ActionResponse).Message) } default: - println("Wrong IPC response type") + fmt.Println("Wrong IPC response type") } }, @@ -188,14 +189,15 @@ var getEnvironmentCmd = &cobra.Command{ switch result := result.(type) { case messages.GetConfigEnvironmentResponse: - fmt.Println("{") - fmt.Println(" \"api\": \"" + result.ApiURL + "\",") - fmt.Println(" \"identity\": \"" + result.IdentityURL + "\",") - fmt.Println(" \"notifications\": \"" + result.NotificationsURL + "\",") - fmt.Println(" \"vault\": \"" + result.VaultURL + "\"") - fmt.Println("}") + response := map[string]string{} + response["api"] = result.ApiURL + response["identity"] = result.IdentityURL + response["notifications"] = result.NotificationsURL + response["vault"] = result.VaultURL + responseJSON, _ := json.Marshal(response) + fmt.Println(string(responseJSON)) default: - println("Wrong IPC response type") + fmt.Println("Wrong IPC response type") } }, } @@ -226,12 +228,12 @@ var setApiClientIDCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Done") + fmt.Println("Done") } else { - println("Setting api client id failed: " + result.(messages.ActionResponse).Message) + fmt.Println("Setting api client id failed: " + result.(messages.ActionResponse).Message) } default: - println("Wrong IPC response type") + fmt.Println("Wrong IPC response type") } }, @@ -263,12 +265,12 @@ var setApiSecretCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Done") + fmt.Println("Done") } else { - println("Setting api secret failed: " + result.(messages.ActionResponse).Message) + fmt.Println("Setting api secret failed: " + result.(messages.ActionResponse).Message) } default: - println("Wrong IPC response type") + fmt.Println("Wrong IPC response type") } }, @@ -289,13 +291,14 @@ var getRuntimeConfigCmd = &cobra.Command{ 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("}") + response := map[string]interface{}{} + response["useMemguard"] = result.UseMemguard + response["SSHAgentSocketPath"] = result.SSHAgentSocketPath + response["goldwardenSocketPath"] = result.GoldwardenSocketPath + responseJSON, _ := json.Marshal(response) + fmt.Println(string(responseJSON)) default: - println("Wrong IPC response type") + fmt.Println("Wrong IPC response type") } }, } diff --git a/cmd/daemonize.go b/cmd/daemonize.go index 8f8dde1..8f4b9e9 100644 --- a/cmd/daemonize.go +++ b/cmd/daemonize.go @@ -19,11 +19,11 @@ var daemonizeCmd = &cobra.Command{ sshDisabled := runtimeConfig.DisableSSHAgent if websocketDisabled { - println("Websocket disabled") + fmt.Println("Websocket disabled") } if sshDisabled { - println("SSH agent disabled") + fmt.Println("SSH agent disabled") } cleanup := func() { diff --git a/cmd/login.go b/cmd/login.go index 746e97f..9fc53b2 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -4,6 +4,8 @@ Copyright © 2023 NAME HERE package cmd import ( + "fmt" + "github.com/quexten/goldwarden/ipc/messages" "github.com/spf13/cobra" ) @@ -17,7 +19,7 @@ var loginCmd = &cobra.Command{ request := messages.DoLoginRequest{} email, _ := cmd.Flags().GetString("email") if email == "" { - println("Error: No email specified") + fmt.Println("Error: No email specified") return } @@ -34,12 +36,12 @@ var loginCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Logged in") + fmt.Println("Logged in") } else { - println("Login failed: " + result.(messages.ActionResponse).Message) + fmt.Println("Login failed: " + result.(messages.ActionResponse).Message) } default: - println("Wrong IPC response type for login") + fmt.Println("Wrong IPC response type for login") } }, } diff --git a/cmd/logins.go b/cmd/logins.go index 15b5c58..0c838cd 100644 --- a/cmd/logins.go +++ b/cmd/logins.go @@ -53,7 +53,7 @@ var getLoginCmd = &cobra.Command{ } break case messages.ActionResponse: - println("Error: " + resp.(messages.ActionResponse).Message) + fmt.Println("Error: " + resp.(messages.ActionResponse).Message) return } }, diff --git a/cmd/pin.go b/cmd/pin.go index d039835..d495147 100644 --- a/cmd/pin.go +++ b/cmd/pin.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/quexten/goldwarden/ipc/messages" "github.com/spf13/cobra" ) @@ -25,12 +27,12 @@ var setPinCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Pin updated") + fmt.Println("Pin updated") } else { - println("Pin updating failed: " + result.(messages.ActionResponse).Message) + fmt.Println("Pin updating failed: " + result.(messages.ActionResponse).Message) } default: - println("Wrong response type") + fmt.Println("Wrong response type") } }, } @@ -48,9 +50,9 @@ var pinStatusCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: - println("Pin status: " + result.(messages.ActionResponse).Message) + fmt.Println("Pin status: " + result.(messages.ActionResponse).Message) default: - println("Wrong response type") + fmt.Println("Wrong response type") } }, } diff --git a/cmd/root.go b/cmd/root.go index f8e2931..b1325aa 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "os" "github.com/quexten/goldwarden/agent" @@ -62,8 +63,8 @@ func loginIfRequired() error { func handleSendToAgentError(err error) { if err != nil { - println("Error: " + err.Error()) - println("Is the daemon running?") + fmt.Println("Error: " + err.Error()) + fmt.Println("Is the daemon running?") return } } diff --git a/cmd/run.go b/cmd/run.go index dfb49c8..381d0c0 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -4,6 +4,7 @@ Copyright © 2023 NAME HERE package cmd import ( + "fmt" "os" "os/exec" @@ -19,7 +20,7 @@ var runCmd = &cobra.Command{ The variables are stored as a secure note. Consult the documentation for more information.`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 1 { - println("Error: No command specified") + fmt.Println("Error: No command specified") return } @@ -43,7 +44,7 @@ var runCmd = &cobra.Command{ env = append(env, key+"="+value) } case messages.ActionResponse: - println("Error: " + result.(messages.ActionResponse).Message) + fmt.Println("Error: " + result.(messages.ActionResponse).Message) return } diff --git a/cmd/send.go b/cmd/send.go index aa098a0..388a535 100644 --- a/cmd/send.go +++ b/cmd/send.go @@ -39,7 +39,7 @@ var sendCreateCmd = &cobra.Command{ fmt.Println("Send created: " + result.(messages.CreateSendResponse).URL) break case messages.ActionResponse: - println("Error: " + result.(messages.ActionResponse).Message) + fmt.Println("Error: " + result.(messages.ActionResponse).Message) return } }, diff --git a/cmd/session.go b/cmd/session.go new file mode 100644 index 0000000..7b4ee48 --- /dev/null +++ b/cmd/session.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/quexten/goldwarden/ipc/messages" + "github.com/spf13/cobra" +) + +// sessionCmd represents the run command +var sessionCmd = &cobra.Command{ + Use: "session", + Hidden: true, + Short: "Starts a new session", + Long: `Starts a new session.`, + Run: func(cmd *cobra.Command, args []string) { + for { + reader := bufio.NewReader(os.Stdin) + text, _ := reader.ReadString('\n') + text = strings.TrimSuffix(text, "\n") + args := strings.Split(text, " ") + rootCmd.SetArgs(args) + rootCmd.Execute() + } + }, +} + +var pinentry = &cobra.Command{ + Use: "pinentry", + Hidden: true, + Short: "Registers as a pinentry program", + Long: `Registers as a pinentry program.`, + Run: func(cmd *cobra.Command, args []string) { + + }, +} + +var authenticateSession = &cobra.Command{ + Use: "authenticate-session", + Hidden: true, + Short: "Authenticates a session", + Long: `Authenticates a session.`, + Run: func(cmd *cobra.Command, args []string) { + token := args[0] + response, err := commandClient.SendToAgent(messages.SessionAuthRequest{Token: token}) + if err != nil { + panic(err) + } + fmt.Println(response.(messages.SessionAuthResponse).Verified) + }, +} + +func init() { + rootCmd.AddCommand(sessionCmd) + rootCmd.AddCommand(pinentry) + rootCmd.AddCommand(authenticateSession) +} diff --git a/cmd/ssh.go b/cmd/ssh.go index 376892e..3bb74c7 100644 --- a/cmd/ssh.go +++ b/cmd/ssh.go @@ -47,7 +47,7 @@ var sshAddCmd = &cobra.Command{ } break case messages.ActionResponse: - println("Error: " + result.(messages.ActionResponse).Message) + fmt.Println("Error: " + result.(messages.ActionResponse).Message) return } }, @@ -74,7 +74,7 @@ var listSSHCmd = &cobra.Command{ } break case messages.ActionResponse: - println("Error: " + result.(messages.ActionResponse).Message) + fmt.Println("Error: " + result.(messages.ActionResponse).Message) return } }, diff --git a/cmd/vault.go b/cmd/vault.go index 0da79b3..305ed5d 100644 --- a/cmd/vault.go +++ b/cmd/vault.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "time" @@ -30,12 +31,12 @@ var unlockCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Unlocked") + fmt.Println("Unlocked") } else { - println("Not unlocked: " + result.(messages.ActionResponse).Message) + fmt.Println("Not unlocked: " + result.(messages.ActionResponse).Message) } default: - println("Wrong response type") + fmt.Println("Wrong response type") } }, } @@ -56,12 +57,12 @@ var lockCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Locked") + fmt.Println("Locked") } else { - println("Not locked: " + result.(messages.ActionResponse).Message) + fmt.Println("Not locked: " + result.(messages.ActionResponse).Message) } default: - println("Wrong response type") + fmt.Println("Wrong response type") } }, } @@ -82,12 +83,12 @@ var purgeCmd = &cobra.Command{ switch result.(type) { case messages.ActionResponse: if result.(messages.ActionResponse).Success { - println("Purged") + fmt.Println("Purged") } else { - println("Not purged: " + result.(messages.ActionResponse).Message) + fmt.Println("Not purged: " + result.(messages.ActionResponse).Message) } default: - println("Wrong response type") + fmt.Println("Wrong response type") } }, } @@ -107,18 +108,19 @@ var statusCmd = &cobra.Command{ switch result.(type) { case messages.VaultStatusResponse: + response := map[string]interface{}{} status := result.(messages.VaultStatusResponse) - fmt.Println("{") - fmt.Println(" \"locked\":", status.Locked, ",") - fmt.Println(" \"loginEntries\":", status.NumberOfLogins, ",") - fmt.Println(" \"noteEntries\":", status.NumberOfNotes, ",") - fmt.Println(" \"lastSynced\": \"" + time.Unix(status.LastSynced, 0).String() + "\",") - fmt.Println(" \"websocketConnected\":", status.WebsockedConnected, ",") - fmt.Println(" \"pinSet\":", status.PinSet, ",") - fmt.Println(" \"loggedIn\":", status.LoggedIn) - fmt.Println("}") + response["locked"] = status.Locked + response["loginEntries"] = status.NumberOfLogins + response["noteEntries"] = status.NumberOfNotes + response["lastSynced"] = time.Unix(status.LastSynced, 0).String() + response["websocketConnected"] = status.WebsockedConnected + response["pinSet"] = status.PinSet + response["loggedIn"] = status.LoggedIn + responseJSON, _ := json.Marshal(response) + fmt.Println(string(responseJSON)) default: - println("Wrong response type") + fmt.Println("Wrong response type") } }, } diff --git a/ipc/messages/session.go b/ipc/messages/session.go new file mode 100644 index 0000000..b995897 --- /dev/null +++ b/ipc/messages/session.go @@ -0,0 +1,31 @@ +package messages + +import "encoding/json" + +type SessionAuthRequest struct { + Token string +} + +type SessionAuthResponse struct { + Verified bool +} + +func init() { + registerPayloadParser(func(payload []byte) (interface{}, error) { + var req SessionAuthRequest + err := json.Unmarshal(payload, &req) + if err != nil { + panic("Unmarshal: " + err.Error()) + } + return req, nil + }, SessionAuthRequest{}) + + registerPayloadParser(func(payload []byte) (interface{}, error) { + var req SessionAuthResponse + err := json.Unmarshal(payload, &req) + if err != nil { + panic("Unmarshal: " + err.Error()) + } + return req, nil + }, SessionAuthResponse{}) +} diff --git a/main.go b/main.go index 4c84457..686d103 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os" "strings" @@ -36,6 +37,7 @@ func main() { UseMemguard: os.Getenv("GOLDWARDEN_NO_MEMGUARD") != "true", SSHAgentSocketPath: os.Getenv("GOLDWARDEN_SSH_AUTH_SOCK"), GoldwardenSocketPath: os.Getenv("GOLDWARDEN_SOCKET_PATH"), + DaemonAuthToken: os.Getenv("GOLDWARDEN_DAEMON_AUTH_TOKEN"), ConfigDirectory: configPath, } @@ -62,7 +64,7 @@ func main() { 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) + fmt.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" } diff --git a/ui/goldwarden.py b/ui/goldwarden.py deleted file mode 100644 index 0586d4a..0000000 --- a/ui/goldwarden.py +++ /dev/null @@ -1,184 +0,0 @@ -import subprocess -import json -import os - -# if flatpak -if os.path.exists("/app/bin/goldwarden"): - BINARY_PATH = "/app/bin/goldwarden" -else: - res = subprocess.run(["which", "goldwarden"]) - BINARY_PATH = res.stdout.decode("utf-8").strip() - -def set_api_url(url): - restic_cmd = f"{BINARY_PATH} config set-api-url {url}" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - -def set_identity_url(url): - restic_cmd = f"{BINARY_PATH} config set-identity-url {url}" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - -def set_notification_url(url): - restic_cmd = f"{BINARY_PATH} config set-notifications-url {url}" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - -def set_vault_url(url): - restic_cmd = f"{BINARY_PATH} config set-vault-url {url}" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed err", result.stderr) - -def set_url(url): - restic_cmd = f"{BINARY_PATH} config set-url {url}" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed err", result.stderr) - -def get_environment(): - restic_cmd = f"{BINARY_PATH} config get-environment" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - return None - try: - result_text = result.stdout - print(result_text) - return json.loads(result_text) - except Exception as e: - print(e) - return None - -def set_client_id(client_id): - restic_cmd = f"{BINARY_PATH} config set-client-id \"{client_id}\"" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed err", result.stderr) - -def set_client_secret(client_secret): - restic_cmd = f"{BINARY_PATH} config set-client-secret \"{client_secret}\"" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed err", result.stderr) - -def login_with_password(email, password): - restic_cmd = f"{BINARY_PATH} vault login --email {email}" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - if len(result.stderr.strip()) > 0: - print(result.stderr) - if "password" in result.stderr: - return "badpass" - else: - if "Logged in" in result.stderr: - print("ok") - return "ok" - return "error" - print("ok") - return "ok" - -def login_passwordless(email): - restic_cmd = f"{BINARY_PATH} vault login --email {email} --passwordless" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - -def is_pin_enabled(): - restic_cmd = f"{BINARY_PATH} vault pin status" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - # check if contains enabled - return "enabled" in result.stderr - -def enable_pin(): - restic_cmd = f"{BINARY_PATH} vault pin set" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - -def unlock(): - restic_cmd = f"{BINARY_PATH} vault unlock" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - -def lock(): - restic_cmd = f"{BINARY_PATH} vault lock" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - -def purge(): - restic_cmd = f"{BINARY_PATH} vault purge" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - -def get_vault_status(): - restic_cmd = f"{BINARY_PATH} vault status" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - try: - return json.loads(result.stdout) - except Exception as e: - print(e) - return None - -def get_vault_logins(): - restic_cmd = f"{BINARY_PATH} logins list" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - try: - return json.loads(result.stdout) - 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 - env = os.environ.copy() - env["PASSWORD"] = password - restic_cmd = f"{BINARY_PATH} autotype --username {username}" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True, env=env) - print(result.stderr) - print(result.stdout) - if result.returncode != 0: - raise Exception("Failed to initialize repository, err", result.stderr) - -def is_daemon_running(): - restic_cmd = f"{BINARY_PATH} vault status" - result = subprocess.run(restic_cmd.split(), capture_output=True, text=True) - if result.returncode != 0: - return False - daemon_not_running = ("daemon running?" in result.stderr or "daemon running" in result.stderr) - return not daemon_not_running - -def run_daemon(): - restic_cmd = f"{BINARY_PATH} daemonize" - # print while running - result = subprocess.Popen(restic_cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if result.returncode != 0: - print("Failed err", result.stderr) - for line in result.stdout: - print(line.decode("utf-8")) - result.wait() - print("quitting goldwarden daemon") - return result.returncode \ No newline at end of file diff --git a/ui/main.py b/ui/main.py deleted file mode 100644 index 978722f..0000000 --- a/ui/main.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/python -import time -import subprocess -from tendo import singleton -import monitors.dbus_autofill_monitor -import sys -import goldwarden -from threading import Thread -import os - -isflatpak = os.path.exists("/.flatpak-info") -pathprefix = "/app/bin/" if isflatpak else "./" - -try: - subprocess.Popen(["python3", f'{pathprefix}background.py'], start_new_session=True) -except: - pass - -is_hidden = "--hidden" in sys.argv - -if not is_hidden: - try: - subprocess.Popen(["python3", f'{pathprefix}settings.py'], start_new_session=True) - except: - subprocess.Popen(["python3", f'{pathprefix}settings.py'], start_new_session=True) - pass - -try: - me = singleton.SingleInstance() -except: - exit() - -def run_daemon(): - # todo: do a proper check - if is_hidden: - time.sleep(20) - print("IS daemon running", goldwarden.is_daemon_running()) - if not goldwarden.is_daemon_running(): - print("running daemon") - goldwarden.run_daemon() - print("daemon running") - -thread = Thread(target=run_daemon) -thread.start() - -def on_autofill(): - subprocess.Popen(["python3", f'{pathprefix}autofill.py'], start_new_session=True) - -monitors.dbus_autofill_monitor.on_autofill = lambda: on_autofill() -monitors.dbus_autofill_monitor.run_daemon() - -while True: - time.sleep(60) \ No newline at end of file diff --git a/ui/src/goldwarden_ui_main.py b/ui/src/goldwarden_ui_main.py new file mode 100644 index 0000000..2f418d7 --- /dev/null +++ b/ui/src/goldwarden_ui_main.py @@ -0,0 +1,3 @@ +import linux.main as linux_main + +linux_main.main() \ No newline at end of file diff --git a/ui/autotype/autotype.py b/ui/src/linux/autotype/autotype.py similarity index 100% rename from ui/autotype/autotype.py rename to ui/src/linux/autotype/autotype.py diff --git a/ui/background.py b/ui/src/linux/background.py similarity index 100% rename from ui/background.py rename to ui/src/linux/background.py diff --git a/ui/clipboard.py b/ui/src/linux/clipboard.py similarity index 100% rename from ui/clipboard.py rename to ui/src/linux/clipboard.py diff --git a/ui/src/linux/main.py b/ui/src/linux/main.py new file mode 100644 index 0000000..0e92bab --- /dev/null +++ b/ui/src/linux/main.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +import time +import subprocess +from tendo import singleton +from .monitors import dbus_autofill_monitor +from .monitors import dbus_monitor +import sys +from services import goldwarden +from threading import Thread +import os +import secrets +import time +import os + +root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir)) + +def main(): + token = secrets.token_hex(32) + print("token", token) + if not os.environ.get("GOLDWARDEN_DAEMON_AUTH_TOKEN") == None: + token = os.environ["GOLDWARDEN_DAEMON_AUTH_TOKEN"] + + # check if already running + try: + me = singleton.SingleInstance() + except: + import dbus + + bus = dbus.SessionBus() + the_object = bus.get_objeect("com.quexten.Goldwarden.dbus", "/com/quexten/Goldwarden") + the_interface = dbus.Interface(the_object, "com.quexten.Goldwarden.Settings") + reply = the_interface.settings() + exit() + + # start daemons + dbus_autofill_monitor.run_daemon() # todo: remove after migration + dbus_monitor.run_daemon(token) + + if not "--hidden" in sys.argv: + subprocess.Popen(["python3", "-m", "src.ui.settings"], cwd=root_path, start_new_session=True) + + # try: + # subprocess.Popen(["python3", f'{source_path}/background.py'], start_new_session=True) + # except Exception as e: + # pass + + while True: + time.sleep(60) + + +# def run_daemon(): +# # todo: do a proper check +# if is_hidden: +# time.sleep(20) +# print("IS daemon running", goldwarden.is_daemon_running()) +# if not goldwarden.is_daemon_running(): +# print("running daemon") +# goldwarden.run_daemon() +# print("daemon running") diff --git a/ui/monitors/dbus_autofill_monitor.py b/ui/src/linux/monitors/dbus_autofill_monitor.py similarity index 91% rename from ui/monitors/dbus_autofill_monitor.py rename to ui/src/linux/monitors/dbus_autofill_monitor.py index e7a29a3..1090074 100644 --- a/ui/monitors/dbus_autofill_monitor.py +++ b/ui/src/linux/monitors/dbus_autofill_monitor.py @@ -19,8 +19,13 @@ class GoldwardenDBUSService(dbus.service.Object): on_autofill() return "" -def run_daemon(): +def daemon(): DBusGMainLoop(set_as_default=True) service = GoldwardenDBUSService() from gi.repository import GLib, GObject as gobject gobject.MainLoop().run() + +def run_daemon(): + thread = Thread(target=daemon) + thread.start() + \ No newline at end of file diff --git a/ui/src/linux/monitors/dbus_monitor.py b/ui/src/linux/monitors/dbus_monitor.py new file mode 100644 index 0000000..0af5791 --- /dev/null +++ b/ui/src/linux/monitors/dbus_monitor.py @@ -0,0 +1,41 @@ +from gi.repository import Gtk +import dbus +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop +from threading import Thread +import subprocess +import os + +root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, os.pardir)) +daemon_token = None + +class GoldwardenDBUSService(dbus.service.Object): + def __init__(self): + bus_name = dbus.service.BusName('com.quexten.Goldwarden.ui', bus=dbus.SessionBus()) + dbus.service.Object.__init__(self, bus_name, '/com/quexten/Goldwarden/ui') + + @dbus.service.method('com.quexten.Goldwarden.ui.QuickAccess') + def quickaccess(self): + p = subprocess.Popen(["python3", "-m", "src.ui.quickaccess"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) + print("writing token") + p.stdin.write(f"{daemon_token}\n".encode()) + p.stdin.flush() + print("reading token") + return "" + + @dbus.service.method('com.quexten.Goldwarden.ui.Settings') + def settings(self): + subprocess.Popen(["python3", "-m", "src.ui.settings"], cwd=root_path, start_new_session=True) + return "" + +def daemon(): + DBusGMainLoop(set_as_default=True) + service = GoldwardenDBUSService() + from gi.repository import GLib, GObject as gobject + gobject.MainLoop().run() + +def run_daemon(token): + global daemon_token + daemon_token = token + thread = Thread(target=daemon) + thread.start() \ No newline at end of file diff --git a/ui/monitors/global_shortcut_portal.py b/ui/src/linux/monitors/global_shortcut_portal.py similarity index 100% rename from ui/monitors/global_shortcut_portal.py rename to ui/src/linux/monitors/global_shortcut_portal.py diff --git a/ui/src/services/goldwarden.py b/ui/src/services/goldwarden.py new file mode 100644 index 0000000..5874b74 --- /dev/null +++ b/ui/src/services/goldwarden.py @@ -0,0 +1,159 @@ +import subprocess +import json +import os +from pathlib import Path + +BINARY_PATHS = [ + "/app/bin/goldwarden", + "/usr/bin/goldwarden", + str(Path.home()) + "/go/src/github.com/quexten/goldwarden/goldwarden" +] + +authenticated_connection = None + +def create_authenticated_connection(token): + global authenticated_connection + BINARY_PATH = None + for path in BINARY_PATHS: + if os.path.exists(path): + BINARY_PATH = path + break + if BINARY_PATH == None: + raise Exception("Could not find goldwarden binary") + authenticated_connection = subprocess.Popen([f"{BINARY_PATH}", "session"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + if not token == None: + authenticated_connection.stdin.write("authenticate-session " + token + "\n") + authenticated_connection.stdin.flush() + # read entire message + result = authenticated_connection.stdout.readline() + if "true" not in result: + raise Exception("Failed to authenticate") + +def send_authenticated_command(cmd): + if authenticated_connection == None: + print("No daemon connection running, please restart the application completely.") + return "" + + print("sending command", cmd) + authenticated_connection.stdin.write(cmd + "\n") + authenticated_connection.stdin.flush() + result = authenticated_connection.stdout.readline() + print("result", result) + return result + +def set_api_url(url): + send_authenticated_command(f"config set-api-url {url}") + +def set_identity_url(url): + send_authenticated_command(f"config set-identity-url {url}") + +def set_notification_url(url): + send_authenticated_command(f"config set-notifications-url {url}") + +def set_vault_url(url): + send_authenticated_command(f"config set-vault-url {url}") + +def set_url(url): + send_authenticated_command(f"config set-url {url}") + +def get_environment(): + result = send_authenticated_command(f"config get-environment") + try: + return json.loads(result) + except Exception as e: + print(e) + return None + +def set_client_id(client_id): + send_authenticated_command(f"config set-client-id \"{client_id}\"") + +def set_client_secret(client_secret): + send_authenticated_command(f"config set-client-secret \"{client_secret}\"") + +def login_with_password(email, password): + result = send_authenticated_command(f"vault login --email {email}") + if result.returncode != 0: + raise Exception("Failed to initialize repository, err", result.stderr) + if len(result.stderr.strip()) > 0: + print(result.stderr) + if "password" in result.stderr: + return "badpass" + else: + if "Logged in" in result.stderr: + print("ok") + return "ok" + return "error" + print("ok") + return "ok" + +def login_passwordless(email): + send_authenticated_command(f"vault login --email {email} --passwordless") + +def is_pin_enabled(): + result = send_authenticated_command("vault pin status") + return "enabled" in result + +def enable_pin(): + send_authenticated_command(f"vault pin set") + +def unlock(): + send_authenticated_command(f"vault unlock") + +def lock(): + send_authenticated_command(f"vault lock") + +def purge(): + send_authenticated_command(f"vault purge") + +def get_vault_status(): + result = send_authenticated_command(f"vault status") + try: + return json.loads(result) + except Exception as e: + print(e) + return None + +def get_vault_logins(): + result = send_authenticated_command(f"logins list") + if result.returncode != 0: + raise Exception("Failed to initialize repository, err", result.stderr) + try: + return json.loads(result) + except Exception as e: + return None + +def get_runtime_config(): + result = send_authenticated_command(f"config get-runtime-config") + print(result) + try: + return json.loads(result) + except Exception as e: + return None + +# def autotype(username, password): +# # environment +# env = os.environ.copy() +# env["PASSWORD"] = password +# restic_cmd = f"{BINARY_PATH} autotype --username {username}" +# result = subprocess.run(restic_cmd.split(), capture_output=True, text=True, env=env) +# print(result.stderr) +# print(result.stdout) +# if result.returncode != 0: +# raise Exception("Failed to initialize repository, err", result.stderr) + +def is_daemon_running(): + result = send_authenticated_command(f"vault status") + daemon_not_running = ("daemon running" in result) + return not daemon_not_running + +# def run_daemon(): +# restic_cmd = f"daemonize" +# # print while running +# result = subprocess.Popen(restic_cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) +# if result.returncode != 0: +# print("Failed err", result.stderr) +# for line in result.stdout: +# print(line.decode("utf-8")) +# result.wait() +# print("quitting goldwarden daemon") +# return result.returncode \ No newline at end of file diff --git a/ui/totp.py b/ui/src/services/totp.py similarity index 100% rename from ui/totp.py rename to ui/src/services/totp.py diff --git a/ui/components.py b/ui/src/ui/components.py similarity index 100% rename from ui/components.py rename to ui/src/ui/components.py diff --git a/ui/autofill.py b/ui/src/ui/quickaccess.py similarity index 94% rename from ui/autofill.py rename to ui/src/ui/quickaccess.py index 3e696d6..55edb9b 100644 --- a/ui/autofill.py +++ b/ui/src/ui/quickaccess.py @@ -4,14 +4,17 @@ gi.require_version('Adw', '1') import gc import time from gi.repository import Gtk, Adw, GLib, Notify, Gdk -import goldwarden -import clipboard +from ..services import goldwarden +from ..linux import clipboard from threading import Thread import sys import os -import totp +from ..services import totp Notify.init("Goldwarden") +token = sys.stdin.readline() +goldwarden.create_authenticated_connection(token) + class MyApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) @@ -85,6 +88,9 @@ class MainWindow(Gtk.ApplicationWindow): keycont = Gtk.EventControllerKey() def handle_keypress(cotroller, keyval, keycode, state, user_data): + # if ctrl is pressed + if state == 4: + print("ctrl") if keycode == 36: print("enter") self.hide() @@ -130,7 +136,8 @@ class MainWindow(Gtk.ApplicationWindow): self.history_list.get_style_context().add_class("boxed-list") self.box.append(self.history_list) self.set_default_size(700, 700) - self.set_title("Goldwarden") + # self.set_title("Goldwarden") + self.set_title(token) app = MyApp(application_id="com.quexten.Goldwarden.autofill-menu") app.run(sys.argv) \ No newline at end of file diff --git a/ui/settings.py b/ui/src/ui/settings.py similarity index 99% rename from ui/settings.py rename to ui/src/ui/settings.py index b122e7d..563460f 100644 --- a/ui/settings.py +++ b/ui/src/ui/settings.py @@ -6,12 +6,14 @@ gi.require_version('Adw', '1') import gc from gi.repository import Gtk, Adw, GLib, Gdk -import goldwarden +from ..services import goldwarden from threading import Thread import subprocess -import components +from . import components import os +goldwarden.create_authenticated_connection(None) + class SettingsWinvdow(Gtk.ApplicationWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -201,6 +203,7 @@ class SettingsWinvdow(Gtk.ApplicationWindow): pin_set = goldwarden.is_pin_enabled() status = goldwarden.get_vault_status() + print("status", status) runtimeCfg = goldwarden.get_runtime_config() if runtimeCfg != None: self.ssh_row.set_subtitle("Listening at "+runtimeCfg["SSHAgentSocketPath"])