mirror of
https://github.com/quexten/goldwarden.git
synced 2024-11-23 21:44:37 +03:00
Merge pull request #100 from quexten/feature/authenticated-connection
Authenticated Session & External Pinentry
This commit is contained in:
commit
52156f8892
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,4 +3,5 @@ goldwarden
|
||||
__pycache__
|
||||
.flatpak-builder
|
||||
flatpak-pip-generator
|
||||
repo
|
||||
repo
|
||||
__debug*
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -49,6 +49,7 @@ type RuntimeConfig struct {
|
||||
UseMemguard bool
|
||||
SSHAgentSocketPath string
|
||||
GoldwardenSocketPath string
|
||||
DaemonAuthToken string
|
||||
}
|
||||
|
||||
type ConfigFile struct {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package client
|
||||
|
||||
type Client interface {
|
||||
SendToAgent(request interface{}) (interface{}, error)
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -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() {
|
||||
|
10
cmd/login.go
10
cmd/login.go
@ -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")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
|
12
cmd/pin.go
12
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")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
16
cmd/root.go
16
cmd/root.go
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
90
cmd/session.go
Normal 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)
|
||||
}
|
@ -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
|
||||
}
|
||||
},
|
||||
|
40
cmd/vault.go
40
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")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -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
|
@ -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
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
4
gui/goldwarden_ui_main.py
Normal file
4
gui/goldwarden_ui_main.py
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
import src.linux.main as linux_main
|
||||
|
||||
linux_main.main()
|
14
gui/python3-requirements.json
Normal file
14
gui/python3-requirements.json
Normal 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
1
gui/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
tendo==0.3.0
|
69
gui/src/gui/pinentry.py
Normal file
69
gui/src/gui/pinentry.py
Normal 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)
|
63
gui/src/gui/pinentry_approval.py
Normal file
63
gui/src/gui/pinentry_approval.py
Normal 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)
|
@ -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)
|
@ -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
55
gui/src/linux/main.py
Normal 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)
|
@ -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()
|
||||
|
41
gui/src/linux/monitors/dbus_monitor.py
Normal file
41
gui/src/linux/monitors/dbus_monitor.py
Normal 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()
|
196
gui/src/services/goldwarden.py
Normal file
196
gui/src/services/goldwarden.py
Normal 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()
|
34
gui/src/services/pinentry.py
Normal file
34
gui/src/services/pinentry.py
Normal 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
108
ipc/messages/session.go
Normal 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{})
|
||||
}
|
2
main.go
2
main.go
@ -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"
|
||||
}
|
||||
|
53
ui/main.py
53
ui/main.py
@ -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)
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
ChaCha20==1.1.1
|
||||
chacha20poly1305==0.0.3
|
||||
tendo==0.3.0
|
Loading…
Reference in New Issue
Block a user