Merge pull request #100 from quexten/feature/authenticated-connection

Authenticated Session & External Pinentry
This commit is contained in:
Bernd Schoolmann 2024-02-09 17:36:52 +01:00 committed by GitHub
commit 52156f8892
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 1145 additions and 491 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ goldwarden
__pycache__
.flatpak-builder
flatpak-pip-generator
repo
repo
__debug*

View File

@ -97,16 +97,16 @@ func handleGetLoginCipher(request messages.IPCMessage, cfg *config.Config, vault
}
func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (response messages.IPCMessage, err error) {
if approved, err := pinentry.GetApproval("Access Vault", fmt.Sprintf("%s on %s>%s>%s is trying access ALL CREDENTIALS", ctx.UserName, ctx.GrandParentProcessName, ctx.ParentProcessName, ctx.ProcessName)); err != nil || !approved {
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
Success: false,
Message: "not approved",
})
if err != nil {
return messages.IPCMessage{}, err
}
return response, nil
}
// if approved, err := pinentry.GetApproval("Access Vault", fmt.Sprintf("%s on %s>%s>%s is trying access ALL CREDENTIALS", ctx.UserName, ctx.GrandParentProcessName, ctx.ParentProcessName, ctx.ProcessName)); err != nil || !approved {
// response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
// Success: false,
// Message: "not approved",
// })
// if err != nil {
// return messages.IPCMessage{}, err
// }
// return response, nil
// }
logins := vault.GetLogins()
decryptedLoginCiphers := make([]messages.DecryptedLoginCipher, 0)

View File

@ -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

View File

@ -49,6 +49,7 @@ type RuntimeConfig struct {
UseMemguard bool
SSHAgentSocketPath string
GoldwardenSocketPath string
DaemonAuthToken string
}
type ConfigFile struct {

View File

@ -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

View File

@ -8,7 +8,7 @@ import (
"github.com/twpayne/go-pinentry"
)
func GetPassword(title string, description string) (string, error) {
func getPassword(title string, description string) (string, error) {
client, err := pinentry.NewClient(
pinentry.WithBinaryNameFromGnuPGAgentConf(),
pinentry.WithGPGTTY(),
@ -38,7 +38,7 @@ func GetPassword(title string, description string) (string, error) {
}
}
func GetApproval(title string, description string) (bool, error) {
func getApproval(title string, description string) (bool, error) {
if systemAuthDisabled {
return true, nil
}

View File

@ -10,7 +10,7 @@ import (
pinentry "github.com/quexten/goldwarden/agent/systemauth/pinentry/keybase-pinentry"
)
func GetPassword(title string, description string) (string, error) {
func getPassword(title string, description string) (string, error) {
pinentryInstance := pinentry.New("", logger.New(""), "")
result, err := pinentryInstance.Get(keybase1.SecretEntryArg{
Prompt: title,
@ -28,7 +28,7 @@ func GetPassword(title string, description string) (string, error) {
return result.Text, nil
}
func GetApproval(title string, description string) (bool, error) {
func getApproval(title string, description string) (bool, error) {
pinentryInstance := pinentry.New("", logger.New(""), "")
result, err := pinentryInstance.Get(keybase1.SecretEntryArg{
Prompt: title,

View File

@ -1,6 +1,7 @@
package pinentry
import (
"errors"
"os"
"github.com/quexten/goldwarden/logging"
@ -9,8 +10,52 @@ import (
var log = logging.GetLogger("Goldwarden", "Pinentry")
var systemAuthDisabled = false
type Pinentry struct {
GetPassword func(title string, description string) (string, error)
GetApproval func(title string, description string) (bool, error)
}
var externalPinentry Pinentry = Pinentry{}
func init() {
if os.Getenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED") == "true" {
systemAuthDisabled = true
}
}
func SetExternalPinentry(pinentry Pinentry) error {
if externalPinentry.GetPassword != nil {
return errors.New("External pinentry already set")
}
externalPinentry = pinentry
return nil
}
func GetPassword(title string, description string) (string, error) {
password, err := getPassword(title, description)
if err == nil {
return password, nil
}
if externalPinentry.GetPassword != nil {
return externalPinentry.GetPassword(title, description)
}
// return "", errors.New("Not implemented")
return password, nil
}
func GetApproval(title string, description string) (bool, error) {
approval, err := getApproval(title, description)
if err == nil {
return approval, nil
}
if externalPinentry.GetApproval != nil {
return externalPinentry.GetApproval(title, description)
}
// return true, errors.New("Not implemented")
return approval, nil
}

View File

@ -4,12 +4,12 @@ package pinentry
import "errors"
func GetPassword(title string, description string) (string, error) {
func getPassword(title string, description string) (string, error) {
log.Info("Asking for password is not implemented on this platform")
return "", errors.New("Not implemented")
}
func GetApproval(title string, description string) (bool, error) {
func getApproval(title string, description string) (bool, error) {
log.Info("Asking for approval is not implemented on this platform")
return true, errors.New("Not implemented")
}

View File

@ -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

View File

@ -16,6 +16,8 @@ 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/systemauth/pinentry"
"github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/ipc/messages"
"github.com/quexten/goldwarden/logging"
@ -44,7 +46,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 +63,204 @@ func serveAgentSession(c net.Conn, ctx context.Context, vault *vault.Vault, cfg
continue
}
responseBytes := []byte{}
// todo refactor to other file
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
}
// todo refactor to other file
if msg.Type == messages.MessageTypeForEmptyPayload(messages.PinentryRegistrationRequest{}) {
log.Info("Received pinentry registration request")
getPasswordChan := make(chan struct {
title string
description string
})
getPasswordReturnChan := make(chan struct {
password string
err error
})
getApprovalChan := make(chan struct {
title string
description string
})
getApprovalReturnChan := make(chan struct {
approved bool
err error
})
pe := pinentry.Pinentry{
GetPassword: func(title string, description string) (string, error) {
getPasswordChan <- struct {
title string
description string
}{title, description}
returnValue := <-getPasswordReturnChan
return returnValue.password, returnValue.err
},
GetApproval: func(title string, description string) (bool, error) {
getApprovalChan <- struct {
title string
description string
}{title, description}
returnValue := <-getApprovalReturnChan
return returnValue.approved, returnValue.err
},
}
pinnentrySetError := pinentry.SetExternalPinentry(pe)
payload := messages.PinentryRegistrationResponse{
Success: pinnentrySetError == nil,
}
log.Info("Pinentry registration success: %t", payload.Success)
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())
}
_, err = c.Write([]byte("\n"))
time.Sleep(50 * time.Millisecond) //todo fix properly
if pinnentrySetError != nil {
return
}
for {
fmt.Println("Waiting for pinentry request")
select {
case getPasswordRequest := <-getPasswordChan:
log.Info("Received getPassword request")
payload := messages.PinentryPinRequest{
Message: getPasswordRequest.description,
}
payloadPayload, err := messages.IPCMessageFromPayload(payload)
if err != nil {
writeError(c, err)
continue
}
payloadBytes, err := json.Marshal(payloadPayload)
if err != nil {
writeError(c, err)
continue
}
_, err = c.Write(payloadBytes)
if err != nil {
log.Error("Failed writing to socket " + err.Error())
}
buf := make([]byte, 1024*1024)
nr, err := c.Read(buf)
if err != nil {
return
}
data := buf[0:nr]
var msg messages.IPCMessage
err = json.Unmarshal(data, &msg)
if err != nil {
writeError(c, err)
continue
}
if msg.Type == messages.MessageTypeForEmptyPayload(messages.PinentryPinResponse{}) {
getPasswordResponse := messages.ParsePayload(msg).(messages.PinentryPinResponse)
getPasswordReturnChan <- struct {
password string
err error
}{getPasswordResponse.Pin, nil}
}
case getApprovalRequest := <-getApprovalChan:
log.Info("Received getApproval request")
payload := messages.PinentryApprovalRequest{
Message: getApprovalRequest.description,
}
payloadPayload, err := messages.IPCMessageFromPayload(payload)
if err != nil {
writeError(c, err)
continue
}
payloadBytes, err := json.Marshal(payloadPayload)
if err != nil {
writeError(c, err)
continue
}
_, err = c.Write(payloadBytes)
if err != nil {
log.Error("Failed writing to socket " + err.Error())
}
buf := make([]byte, 1024*1024)
nr, err := c.Read(buf)
if err != nil {
return
}
data := buf[0:nr]
var msg messages.IPCMessage
err = json.Unmarshal(data, &msg)
if err != nil {
writeError(c, err)
continue
}
if msg.Type == messages.MessageTypeForEmptyPayload(messages.PinentryApprovalResponse{}) {
getApprovalResponse := messages.ParsePayload(msg).(messages.PinentryApprovalResponse)
getApprovalReturnChan <- struct {
approved bool
err error
}{getApprovalResponse.Approved, nil}
}
}
}
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 +490,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 +500,10 @@ 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())
}
go serveAgentSession(fd, ctx, vault, &cfg)
go serveAgentSession(fd, vault, &cfg)
}
}()

View File

@ -1,169 +0,0 @@
package agent
import (
"context"
"encoding/json"
"fmt"
"os/user"
"time"
"github.com/quexten/goldwarden/agent/actions"
"github.com/quexten/goldwarden/agent/bitwarden"
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/processsecurity"
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/ipc/messages"
)
func writeErrorToLog(err error) {
log.Error(err.Error())
}
func serveVirtualAgent(recv chan []byte, send chan []byte, ctx context.Context, vault *vault.Vault, cfg *config.Config) {
for {
data := <-recv
var msg messages.IPCMessage
err := json.Unmarshal(data, &msg)
if err != nil {
writeErrorToLog(err)
continue
}
responseBytes := []byte{}
if action, actionFound := actions.AgentActionsRegistry.Get(msg.Type); actionFound {
user, _ := user.Current()
process := "goldwarden"
parent := "SINGLE_PROC_MODE"
grandparent := "SINGLE_PROC_MODE"
callingContext := sockets.CallingContext{
UserName: user.Name,
ProcessName: process,
ParentProcessName: parent,
GrandParentProcessName: grandparent,
}
payload, err := action(msg, cfg, vault, &callingContext)
if err != nil {
writeErrorToLog(err)
continue
}
responseBytes, err = json.Marshal(payload)
if err != nil {
writeErrorToLog(err)
continue
}
} else {
payload := messages.ActionResponse{
Success: false,
Message: "Action not found",
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
writeErrorToLog(err)
continue
}
responseBytes = payloadBytes
}
send <- responseBytes
}
}
func StartVirtualAgent(runtimeConfig config.RuntimeConfig) (chan []byte, chan []byte) {
ctx := context.Background()
var keyring crypto.Keyring
if runtimeConfig.UseMemguard {
keyring = crypto.NewMemguardKeyring(nil)
} else {
keyring = crypto.NewMemoryKeyring(nil)
}
var vault = vault.NewVault(&keyring)
cfg, err := config.ReadConfig(runtimeConfig)
if err != nil {
var cfg = config.DefaultConfig(runtimeConfig.UseMemguard)
cfg.WriteConfig()
}
cfg.ConfigFile.RuntimeConfig = runtimeConfig
if cfg.ConfigFile.RuntimeConfig.ApiURI != "" {
cfg.ConfigFile.ApiUrl = cfg.ConfigFile.RuntimeConfig.ApiURI
}
if cfg.ConfigFile.RuntimeConfig.IdentityURI != "" {
cfg.ConfigFile.IdentityUrl = cfg.ConfigFile.RuntimeConfig.IdentityURI
}
if cfg.ConfigFile.RuntimeConfig.NotificationsURI != "" {
cfg.ConfigFile.NotificationsUrl = cfg.ConfigFile.RuntimeConfig.NotificationsURI
}
if cfg.ConfigFile.RuntimeConfig.DeviceUUID != "" {
cfg.ConfigFile.DeviceUUID = cfg.ConfigFile.RuntimeConfig.DeviceUUID
}
if !cfg.IsLocked() && !cfg.ConfigFile.RuntimeConfig.DoNotPersistConfig {
log.Warn("Config is not locked. SET A PIN!!")
token, err := cfg.GetToken()
if err == nil {
if token.AccessToken != "" {
bitwarden.RefreshToken(ctx, &cfg)
userSymmetricKey, err := cfg.GetUserSymmetricKey()
if err != nil {
fmt.Println(err)
}
var protectedUserSymetricKey crypto.SymmetricEncryptionKey
if keyring.IsMemguard {
protectedUserSymetricKey, err = crypto.MemguardSymmetricEncryptionKeyFromBytes(userSymmetricKey)
} else {
protectedUserSymetricKey, err = crypto.MemorySymmetricEncryptionKeyFromBytes(userSymmetricKey)
}
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, &protectedUserSymetricKey, true)
if err != nil {
fmt.Println(err)
}
}
}
}
processsecurity.DisableDumpable()
err = processsecurity.MonitorLocks(func() {
cfg.Lock()
vault.Clear()
vault.Keyring.Lock()
})
if err != nil {
log.Warn("Could not monitor screensaver: %s", err.Error())
}
go func() {
for {
time.Sleep(TokenRefreshInterval)
if !cfg.IsLocked() {
bitwarden.RefreshToken(ctx, &cfg)
}
}
}()
go func() {
for {
time.Sleep(FullSyncInterval)
if !cfg.IsLocked() {
token, err := cfg.GetToken()
if err != nil {
log.Warn("Could not get token: %s", err.Error())
continue
}
bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, nil, false)
}
}
}()
recv := make(chan []byte)
send := make(chan []byte)
go func() {
go serveVirtualAgent(recv, send, ctx, vault, &cfg)
}()
return recv, send
}

View File

@ -1,5 +0,0 @@
package client
type Client interface {
SendToAgent(request interface{}) (interface{}, error)
}

View File

@ -3,7 +3,6 @@ package client
import (
"encoding/json"
"io"
"log"
"net"
"github.com/quexten/goldwarden/agent/config"
@ -16,13 +15,17 @@ type UnixSocketClient struct {
runtimeConfig *config.RuntimeConfig
}
type UnixSocketConnection struct {
conn net.Conn
}
func NewUnixSocketClient(runtimeConfig *config.RuntimeConfig) UnixSocketClient {
return UnixSocketClient{
runtimeConfig: runtimeConfig,
}
}
func reader(r io.Reader) interface{} {
func Reader(r io.Reader) interface{} {
buf := make([]byte, READ_BUFFER)
for {
n, err := r.Read(buf[:])
@ -40,25 +43,49 @@ func reader(r io.Reader) interface{} {
}
func (client UnixSocketClient) SendToAgent(request interface{}) (interface{}, error) {
c, err := net.Dial("unix", client.runtimeConfig.GoldwardenSocketPath)
c, err := client.Connect()
if err != nil {
return nil, err
}
defer c.Close()
message, err := messages.IPCMessageFromPayload(request)
if err != nil {
panic(err)
}
messageJson, err := json.Marshal(message)
if err != nil {
panic(err)
}
_, err = c.Write(messageJson)
if err != nil {
log.Fatal("write error:", err)
}
result := reader(c)
return messages.ParsePayload(result.(messages.IPCMessage)), nil
return c.SendCommand(request)
}
func (client UnixSocketClient) Connect() (UnixSocketConnection, error) {
c, err := net.Dial("unix", client.runtimeConfig.GoldwardenSocketPath)
if err != nil {
return UnixSocketConnection{}, err
}
return UnixSocketConnection{conn: c}, nil
}
func (conn UnixSocketConnection) SendCommand(request interface{}) (interface{}, error) {
err := conn.WriteMessage(request)
if err != nil {
return nil, err
}
return conn.ReadMessage(), nil
}
func (conn UnixSocketConnection) ReadMessage() interface{} {
result := Reader(conn.conn)
payload := messages.ParsePayload(result.(messages.IPCMessage))
return payload
}
func (conn UnixSocketConnection) WriteMessage(message interface{}) error {
messagePacket, err := messages.IPCMessageFromPayload(message)
if err != nil {
panic(err)
}
messageJson, err := json.Marshal(messagePacket)
if err != nil {
panic(err)
}
_, err = conn.conn.Write(messageJson)
return err
}
func (conn UnixSocketConnection) Close() {
conn.conn.Close()
}

View File

@ -1,45 +0,0 @@
package client
import (
"encoding/json"
"github.com/quexten/goldwarden/ipc/messages"
)
func NewVirtualClient(recv chan []byte, send chan []byte) VirtualClient {
return VirtualClient{
recv,
send,
}
}
type VirtualClient struct {
recv chan []byte
send chan []byte
}
func virtualReader(recv chan []byte) interface{} {
for {
var message messages.IPCMessage
err := json.Unmarshal(<-recv, &message)
if err != nil {
panic(err)
}
return message
}
}
func (client VirtualClient) SendToAgent(request interface{}) (interface{}, error) {
message, err := messages.IPCMessageFromPayload(request)
if err != nil {
panic(err)
}
messageJson, err := json.Marshal(message)
if err != nil {
panic(err)
}
client.send <- messageJson
result := virtualReader(client.recv)
return messages.ParsePayload(result.(messages.IPCMessage)), nil
}

View File

@ -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

View File

@ -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")
}
},
}

View File

@ -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() {

View File

@ -4,6 +4,8 @@ Copyright © 2023 NAME HERE <EMAIL ADDRESS>
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")
}
},
}

View File

@ -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
}
},
@ -72,8 +72,8 @@ var listLoginsCmd = &cobra.Command{
return
}
fmt.Println("[")
for index, login := range logins {
var toPrintLogins []map[string]string
for _, login := range logins {
data := map[string]string{
"name": stringsx.Clean(login.Name),
"uuid": stringsx.Clean(login.UUID),
@ -82,23 +82,14 @@ var listLoginsCmd = &cobra.Command{
"totp": stringsx.Clean(login.TOTPSeed),
"uri": stringsx.Clean(login.URI),
}
jsonString, err := json.Marshal(data)
if err != nil {
handleSendToAgentError(err)
return
}
fmt.Print(string(jsonString))
if index != len(logins)-1 {
fmt.Println(",")
} else {
fmt.Println()
}
toPrintLogins = append(toPrintLogins, data)
}
fmt.Println("]")
toPrintJSON, _ := json.Marshal(toPrintLogins)
fmt.Println(string(toPrintJSON))
},
}
func ListLogins(client client.Client) ([]messages.DecryptedLoginCipher, error) {
func ListLogins(client client.UnixSocketClient) ([]messages.DecryptedLoginCipher, error) {
resp, err := client.SendToAgent(messages.ListLoginsRequest{})
if err != nil {
return []messages.DecryptedLoginCipher{}, err

View File

@ -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")
}
},
}

View File

@ -1,16 +1,16 @@
package cmd
import (
"fmt"
"os"
"github.com/quexten/goldwarden/agent"
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/client"
"github.com/quexten/goldwarden/ipc/messages"
"github.com/spf13/cobra"
)
var commandClient client.Client
var commandClient client.UnixSocketClient
var runtimeConfig config.RuntimeConfig
var rootCmd = &cobra.Command{
@ -24,13 +24,7 @@ var rootCmd = &cobra.Command{
func Execute(cfg config.RuntimeConfig) {
runtimeConfig = cfg
goldwardenSingleProcess := os.Getenv("GOLDWARDEN_SINGLE_PROCESS")
if goldwardenSingleProcess == "true" {
recv, send := agent.StartVirtualAgent(runtimeConfig)
commandClient = client.NewVirtualClient(send, recv)
} else {
commandClient = client.NewUnixSocketClient(&cfg)
}
commandClient = client.NewUnixSocketClient(&cfg)
err := rootCmd.Execute()
if err != nil {
@ -62,8 +56,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
}
}

View File

@ -4,6 +4,7 @@ Copyright © 2023 NAME HERE <EMAIL ADDRESS>
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
}

View File

@ -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
}
},

90
cmd/session.go Normal file
View File

@ -0,0 +1,90 @@
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) {
conn, err := commandClient.Connect()
if err != nil {
panic(err)
}
defer conn.Close()
_, err = conn.SendCommand(messages.PinentryRegistrationRequest{})
if err != nil {
panic(err)
}
for {
response := conn.ReadMessage()
switch response.(type) {
case messages.PinentryPinRequest:
fmt.Println("pin-request" + "," + response.(messages.PinentryPinRequest).Message)
case messages.PinentryApprovalRequest:
fmt.Println("approval-request" + "," + response.(messages.PinentryApprovalRequest).Message)
}
// read line
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
text = strings.TrimSuffix(text, "\n")
switch response.(type) {
case messages.PinentryPinRequest:
err = conn.WriteMessage(messages.PinentryPinResponse{Pin: text})
case messages.PinentryApprovalRequest:
err = conn.WriteMessage(messages.PinentryApprovalResponse{Approved: text == "true"})
}
}
},
}
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)
}

View File

@ -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
}
},

View File

@ -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")
}
},
}

View File

@ -2,7 +2,7 @@ id: com.quexten.Goldwarden
runtime: org.gnome.Platform
runtime-version: '45'
sdk: org.gnome.Sdk
command: main.py
command: goldwarden_ui_main.py
finish-args:
# Allow network access for sync
- --share=network
@ -28,7 +28,7 @@ finish-args:
# biometric / user password auth
- --system-talk-name=org.freedesktop.PolicyKit1
modules:
- ./ui/python3-requirements.json
- ./gui/python3-requirements.json
- name: wl-clipboard
buildsystem: meson
config-opts:
@ -40,18 +40,18 @@ modules:
- name: goldwarden-python-ui
buildsystem: simple
build-commands:
- cp -R ./ui/* /app/bin
- chmod +x /app/bin/main.py
- install -D ./ui/com.quexten.Goldwarden.desktop /app/share/applications/com.quexten.Goldwarden.desktop
- install -D ./ui/goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg
- install -Dm644 ./ui/com.quexten.Goldwarden.metainfo.xml -t /app/share/metainfo/
- cp -R ./gui/* /app/bin
- chmod +x /app/bin/goldwarden_ui_main.py
- install -D ./gui/com.quexten.Goldwarden.desktop /app/share/applications/com.quexten.Goldwarden.desktop
- install -D ./gui/goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg
- install -Dm644 ./gui/com.quexten.Goldwarden.metainfo.xml -t /app/share/metainfo/
sources:
- type: dir
path: ./
- name: goldwarden-core-daemon
buildsystem: simple
build-commands:
- install -D goldwarden /app/bin/goldwarden
- install -D goldwarden /app/bin/src/goldwarden
sources:
- type: file
path: ./goldwarden

View File

@ -2,7 +2,7 @@
Name=Goldwarden
Comment=A Bitwarden compatible desktop password manager
Keywords=password;ssh;auto-type;keys;
Exec=main.py
Exec=goldwarden_ui_main.py
Terminal=false
Type=Application
Icon=com.quexten.Goldwarden

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,4 @@
#!/usr/bin/env python3
import src.linux.main as linux_main
linux_main.main()

View File

@ -0,0 +1,14 @@
{
"name": "python3-tendo",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"tendo==0.3.0\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/ce/3f/761077d55732b0b1a673b15d4fdaa947a7c1eb5c9a23b7142df557019823/tendo-0.3.0-py3-none-any.whl",
"sha256": "026b70b355ea4c9da7c2123fa2d5c280c8983c1b34e329ff49260e2e78b93be7"
}
]
}

1
gui/requirements.txt Normal file
View File

@ -0,0 +1 @@
tendo==0.3.0

69
gui/src/gui/pinentry.py Normal file
View File

@ -0,0 +1,69 @@
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
import gc
import time
from gi.repository import Gtk, Adw, GLib, Notify, Gdk
from threading import Thread
import sys
import os
message = sys.stdin.readline()
class MyApp(Adw.Application):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.connect('activate', self.on_activate)
def on_activate(self, app):
self.pinentry_window = MainWindow(application=app)
self.pinentry_window.present()
self.app = app
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.stack = Gtk.Stack()
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.set_child(self.stack)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.stack.add_child(box)
label = Gtk.Label(label=message)
box.append(label)
# Create the password entry
self.password_entry = Gtk.Entry()
self.password_entry.set_placeholder_text("Enter your password")
self.password_entry.set_visibility(False) # Hide the password
box.append(self.password_entry)
# Create a button box for cancel and approve buttons
button_box = Gtk.Box(spacing=6)
box.append(button_box)
# Cancel button
cancel_button = Gtk.Button(label="Cancel")
cancel_button.set_hexpand(True) # Make the button expand horizontally
def on_cancel_button_clicked(button):
print("", flush=True)
os._exit(0)
cancel_button.connect("clicked", on_cancel_button_clicked)
button_box.append(cancel_button)
# Approve button
approve_button = Gtk.Button(label="Approve")
approve_button.set_hexpand(True) # Make the button expand horizontally
def on_approve_button_clicked(button):
print(self.password_entry.get_text(), flush=True)
os._exit(0)
approve_button.connect("clicked", on_approve_button_clicked)
button_box.append(approve_button)
self.set_default_size(700, 200)
self.set_title("Goldwarden Pinentry")
app = MyApp(application_id="com.quexten.Goldwarden.pinentry")
app.run(sys.argv)

View File

@ -0,0 +1,63 @@
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
import gc
import time
from gi.repository import Gtk, Adw, GLib, Notify, Gdk
from threading import Thread
import sys
import os
message = sys.stdin.readline()
class MyApp(Adw.Application):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.connect('activate', self.on_activate)
def on_activate(self, app):
self.pinentry_window = MainWindow(application=app)
self.pinentry_window.present()
self.app = app
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.stack = Gtk.Stack()
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.set_child(self.stack)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.stack.add_child(box)
label = Gtk.Label(label=message)
box.append(label)
# Create a button box for cancel and approve buttons
button_box = Gtk.Box(spacing=6)
box.append(button_box)
# Cancel button
cancel_button = Gtk.Button(label="Cancel")
cancel_button.set_hexpand(True) # Make the button expand horizontally
def on_cancel_button_clicked(button):
print("false", flush=True)
os._exit(0)
cancel_button.connect("clicked", on_cancel_button_clicked)
button_box.append(cancel_button)
# Approve button
approve_button = Gtk.Button(label="Approve")
approve_button.set_hexpand(True) # Make the button expand horizontally
def on_approve_button_clicked(button):
print("true", flush=True)
os._exit(0)
approve_button.connect("clicked", on_approve_button_clicked)
button_box.append(approve_button)
self.set_default_size(700, 200)
self.set_title("Goldwarden Approval")
app = MyApp(application_id="com.quexten.Goldwarden.pinentry")
app.run(sys.argv)

View File

@ -4,28 +4,36 @@ 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)
self.connect('activate', self.on_activate)
def on_activate(self, app):
self.autofill_window = MainWindow(application=app)
self.autofill_window.logins = []
self.autofill_window.present()
def update_logins(self):
logins = goldwarden.get_vault_logins()
if logins == None:
os._exit(0)
return
app.autofill_window.logins = logins
self.app.autofill_window.logins = logins
def on_activate(self, app):
self.autofill_window = MainWindow(application=app)
self.autofill_window.logins = []
self.autofill_window.present()
self.app = app
thread = Thread(target=self.update_logins)
thread.start()
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
@ -85,6 +93,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 +141,7 @@ 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 Quick Access")
app = MyApp(application_id="com.quexten.Goldwarden.autofill-menu")
app.run(sys.argv)

View File

@ -6,12 +6,16 @@ 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
root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir))
token = sys.stdin.readline()
goldwarden.create_authenticated_connection(None)
class SettingsWinvdow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -46,7 +50,11 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
self.autotype_button = Gtk.Button()
self.autotype_button.set_label("Quick Access")
self.autotype_button.set_margin_top(10)
self.autotype_button.connect("clicked", lambda button: subprocess.Popen(["python3", "/app/bin/autofill.py"], start_new_session=True))
def quickaccess_button_clicked():
p = subprocess.Popen(["python3", "-m", "src.gui.quickaccess"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{token}\n".encode())
p.stdin.flush()
self.autotype_button.connect("clicked", lambda button: quickaccess_button_clicked())
self.autotype_button.get_style_context().add_class("suggested-action")
self.action_preferences_group.add(self.autotype_button)
@ -201,6 +209,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"])

55
gui/src/linux/main.py Normal file
View File

@ -0,0 +1,55 @@
#!/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 src.services import goldwarden
from src.services import pinentry
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))
is_flatpak = os.path.exists("/.flatpak-info")
def main():
token = secrets.token_hex(32)
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_object("com.quexten.Goldwarden.gui", "/com/quexten/Goldwarden/gui")
the_interface = dbus.Interface(the_object, "com.quexten.Goldwarden.gui.actions")
reply = the_interface.settings()
exit()
goldwarden.run_daemon_background(token)
# start daemons
dbus_autofill_monitor.run_daemon(token) # todo: remove after migration
dbus_monitor.run_daemon(token)
pinentry.daemonize()
if not "--hidden" in sys.argv:
p = subprocess.Popen(["python3", "-m", "src.gui.settings"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{token}\n".encode())
p.stdin.flush()
if is_flatpak:
# to autostart the appes
try:
subprocess.Popen(["python3", f'{source_path}/background.py'], start_new_session=True)
except Exception as e:
pass
while True:
time.sleep(60)

View File

@ -10,7 +10,7 @@ import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
from threading import Thread
on_autofill = lambda: None
daemon_token = None
class GoldwardenDBUSService(dbus.service.Object):
def __init__(self):
@ -19,11 +19,20 @@ class GoldwardenDBUSService(dbus.service.Object):
@dbus.service.method('com.quexten.Goldwarden.Autofill')
def autofill(self):
on_autofill()
p = subprocess.Popen(["python3", "-m", "src.gui.quickaccess"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{daemon_token}\n".encode())
p.stdin.flush()
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(token):
global daemon_token
daemon_token = token
thread = Thread(target=daemon)
thread.start()

View File

@ -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.gui', bus=dbus.SessionBus())
dbus.service.Object.__init__(self, bus_name, '/com/quexten/Goldwarden/gui')
@dbus.service.method('com.quexten.Goldwarden.gui.actions')
def quickaccess(self):
p = subprocess.Popen(["python3", "-m", "src.gui.quickaccess"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{daemon_token}\n".encode())
p.stdin.flush()
return ""
@dbus.service.method('com.quexten.Goldwarden.gui.actions')
def settings(self):
p = subprocess.Popen(["python3", "-m", "src.gui.settings"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{daemon_token}\n".encode())
p.stdin.flush()
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()

View File

@ -0,0 +1,196 @@
import subprocess
import json
import os
from pathlib import Path
from threading import Thread
is_flatpak = os.path.exists("/.flatpak-info")
log_directory = str(Path.home()) + "/.local/share/goldwarden"
if is_flatpak:
log_directory = str(Path.home()) + "/.var/app/com.quexten.goldwarden/data/goldwarden"
os.makedirs(log_directory, exist_ok=True)
BINARY_PATHS = [
"/app/bin/goldwarden",
"/usr/bin/goldwarden",
str(Path.home()) + "/go/src/github.com/quexten/goldwarden/goldwarden"
]
BINARY_PATH = ""
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 = None
def create_authenticated_connection(token):
global authenticated_connection
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 ""
authenticated_connection.stdin.write(cmd + "\n")
authenticated_connection.stdin.flush()
result = authenticated_connection.stdout.readline()
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:
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:
return None
def get_vault_logins():
result = send_authenticated_command(f"logins list")
try:
return json.loads(result)
except Exception as e:
return None
def get_runtime_config():
result = send_authenticated_command(f"config get-runtime-config")
try:
return json.loads(result)
except Exception as e:
return None
def autotype(username, password):
env = os.environ.copy()
# todo convert to stdin
env["PASSWORD"] = password
goldwarden_cmd = f"{BINARY_PATH} autotype --username {username}"
result = subprocess.run(goldwarden_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 listen_for_pinentry(on_pinentry, on_pin_approval):
print("listening for pinentry", BINARY_PATH)
pinentry_process = subprocess.Popen([f"{BINARY_PATH}", "pinentry"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
while True:
line = pinentry_process.stdout.readline()
# starts with pin-request
if line.startswith("pin-request"):
text = line.split(",")[1].strip()
pin = on_pinentry(text)
if pin == None:
pin = ""
pinentry_process.stdin.write(pin + "\n")
pinentry_process.stdin.flush()
if line.startswith("approval-request"):
text = line.split(",")[1].strip()
approval = on_pin_approval(text)
if approval:
pinentry_process.stdin.write("true\n")
pinentry_process.stdin.flush()
else:
pinentry_process.stdin.write("false\n")
pinentry_process.stdin.flush()
def run_daemon(token):
#todo replace with stdin
daemon_env = os.environ.copy()
daemon_env["GOLDWARDEN_DAEMON_AUTH_TOKEN"] = token
print("starting goldwarden daemon", BINARY_PATH)
# print while running
result = subprocess.Popen([f"{BINARY_PATH}", "daemonize"], stdout=subprocess.PIPE, text=True, env=daemon_env)
# write log to file until process exits
log_file = open(f"{log_directory}/daemon.log", "w")
while result.poll() == None:
# read stdout and stder
stdout = result.stdout.readline()
log_file.write(stdout)
log_file.flush()
log_file.close()
print("quitting goldwarden daemon")
return result.returncode
def run_daemon_background(token):
thread = Thread(target=lambda: run_daemon(token))
thread.start()

View File

@ -0,0 +1,34 @@
import subprocess
import os
from src.services import goldwarden
from threading import Thread
import time
root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir))
def get_pin(message):
p = subprocess.Popen(["python3", "-m", "src.gui.pinentry"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{message}\n".encode())
p.stdin.flush()
pin = p.stdout.readline().decode().strip()
if pin == "":
return None
return pin
def get_approval(message):
p = subprocess.Popen(["python3", "-m", "src.gui.pinentry_approval"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{message}\n".encode())
p.stdin.flush()
result = p.stdout.readline().decode().strip()
if result == "true":
return True
return False
def daemon():
goldwarden.listen_for_pinentry(get_pin, get_approval)
def daemonize():
#todo fix this
time.sleep(3)
thread = Thread(target=daemon)
thread.start()

108
ipc/messages/session.go Normal file
View File

@ -0,0 +1,108 @@
package messages
import "encoding/json"
type SessionAuthRequest struct {
Token string
}
type SessionAuthResponse struct {
Verified bool
}
type PinentryRegistrationRequest struct {
}
type PinentryRegistrationResponse struct {
Success bool
}
type PinentryPinRequest struct {
Message string
}
type PinentryPinResponse struct {
Pin string
}
type PinentryApprovalRequest struct {
Message string
}
type PinentryApprovalResponse struct {
Approved 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{})
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req PinentryRegistrationRequest
err := json.Unmarshal(payload, &req)
if err != nil {
panic("Unmarshal: " + err.Error())
}
return req, nil
}, PinentryRegistrationRequest{})
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req PinentryRegistrationResponse
err := json.Unmarshal(payload, &req)
if err != nil {
panic("Unmarshal: " + err.Error())
}
return req, nil
}, PinentryRegistrationResponse{})
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req PinentryPinRequest
err := json.Unmarshal(payload, &req)
if err != nil {
panic("Unmarshal: " + err.Error())
}
return req, nil
}, PinentryPinRequest{})
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req PinentryPinResponse
err := json.Unmarshal(payload, &req)
if err != nil {
panic("Unmarshal: " + err.Error())
}
return req, nil
}, PinentryPinResponse{})
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req PinentryApprovalRequest
err := json.Unmarshal(payload, &req)
if err != nil {
panic("Unmarshal: " + err.Error())
}
return req, nil
}, PinentryApprovalRequest{})
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req PinentryApprovalResponse
err := json.Unmarshal(payload, &req)
if err != nil {
panic("Unmarshal: " + err.Error())
}
return req, nil
}, PinentryApprovalResponse{})
}

View File

@ -36,6 +36,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 +63,6 @@ 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)
runtimeConfig.SSHAgentSocketPath = userHome + "/.var/app/com.quexten.Goldwarden/data/ssh-auth-sock"
runtimeConfig.GoldwardenSocketPath = userHome + "/.var/app/com.quexten.Goldwarden/data/goldwarden.sock"
}

View File

@ -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)

View File

@ -1,49 +0,0 @@
{
"name": "python3-requirements",
"buildsystem": "simple",
"build-commands": [],
"modules": [
{
"name": "python3-ChaCha20",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"ChaCha20==1.1.1\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/7d/93/2c5c7203bcb1ece177a49d14b32f0e0511f7d4eaf8446c15f7d64af082ad/ChaCha20-1.1.1.tar.gz",
"sha256": "6d6b1ef373058540369c14d2369ab14ee4ffaeae4dc53d8a18b5b617bd06c6bb"
}
]
},
{
"name": "python3-chacha20poly1305",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"chacha20poly1305==0.0.3\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/38/2c/5b4eb73c5cb30ded3082af025e76f529764971c57c02b101e842ff998f63/chacha20poly1305-0.0.3.tar.gz",
"sha256": "f2f005c7cf4638ffa4ff06c02c78748068b642916795c6d16c7cc5e355e70edf"
}
]
},
{
"name": "python3-tendo",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"tendo==0.3.0\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/ce/3f/761077d55732b0b1a673b15d4fdaa947a7c1eb5c9a23b7142df557019823/tendo-0.3.0-py3-none-any.whl",
"sha256": "026b70b355ea4c9da7c2123fa2d5c280c8983c1b34e329ff49260e2e78b93be7"
}
]
}
]
}

View File

@ -1,3 +0,0 @@
ChaCha20==1.1.1
chacha20poly1305==0.0.3
tendo==0.3.0