mirror of
https://github.com/quexten/goldwarden.git
synced 2024-09-11 19:17:33 +03:00
Compare commits
108 Commits
a30eedec7b
...
f77b7ad307
Author | SHA1 | Date | |
---|---|---|---|
|
f77b7ad307 | ||
|
715d04c5ab | ||
|
2da6bcc138 | ||
|
93dc64ae36 | ||
|
0ea0bcee94 | ||
|
8b95af6458 | ||
|
969ee1ff49 | ||
|
b3f701a416 | ||
|
f99d618b33 | ||
|
f894af5fb3 | ||
|
29bd40384b | ||
|
6ee67d6569 | ||
|
13bd29dbf9 | ||
|
623fdcb719 | ||
|
3b2d1fcf11 | ||
|
5cc502580f | ||
|
f7da373fa0 | ||
|
c15447aab3 | ||
|
9ac1d8100a | ||
|
606dd176ef | ||
|
592ff48148 | ||
|
f6c501f0d2 | ||
|
87e13098a0 | ||
|
7e4d5a4b76 | ||
|
873c4fb799 | ||
|
11ad8c4f48 | ||
|
a94d8f052b | ||
|
9c840f3edc | ||
|
761fb91a8a | ||
|
cf9f3f33dd | ||
|
20e71663e3 | ||
|
86848065d1 | ||
|
76baf9eca0 | ||
|
aeb9888f7f | ||
|
c1f21768a1 | ||
|
01f36cc571 | ||
|
dc05cdfee6 | ||
|
69797aa50b | ||
|
35cc409943 | ||
|
f1395d4b6d | ||
|
83b808f44c | ||
|
472c9f59da | ||
|
f224276749 | ||
|
52156f8892 | ||
|
2e42a798e3 | ||
|
8d257c265e | ||
|
a6d3a1026d | ||
|
563ad6fa45 | ||
|
4a3e8ca819 | ||
|
8a41543601 | ||
|
ed6060eae4 | ||
|
417567f549 | ||
|
200bce7d7e | ||
|
53989ebaa3 | ||
|
39d1ec6980 | ||
|
75982ad322 | ||
|
219a8cb849 | ||
|
14618b3d07 | ||
|
ef276a0019 | ||
|
5805931c00 | ||
|
069afeae27 | ||
|
ac0e84a46f | ||
|
8b08d5841a | ||
|
fe64cc7807 | ||
|
ca17380758 | ||
|
cf6221c080 | ||
|
f3196863bb | ||
|
d0e0d66509 | ||
|
b297bf1ea3 | ||
|
cc0c9d40aa | ||
|
b58d256744 | ||
|
db64c3771e | ||
|
81efe5f7e5 | ||
|
2f200a2794 | ||
|
249bbedc13 | ||
|
8a3cb53e77 | ||
|
490c4982c3 | ||
|
2a96b74110 | ||
|
c8e964ebae | ||
|
d655083a8c | ||
|
64f1174474 | ||
|
f135e8b302 | ||
|
4afd6ef9b1 | ||
|
f5c20905a9 | ||
|
6f10836300 | ||
|
01fe7b7ff4 | ||
|
39c175a1f6 | ||
|
1933917634 | ||
|
7949ed63f3 | ||
|
ab9aaf2fe1 | ||
|
f79dba0e54 | ||
|
599cc1003f | ||
|
1e24bce546 | ||
|
1455cf9cb2 | ||
|
e0cb8a9187 | ||
|
f60fcfd408 | ||
|
f3a33eb36c | ||
|
4add545000 | ||
|
7ca312ad53 | ||
|
01ecb54d82 | ||
|
def7f7a6ba | ||
|
25b72e4c43 | ||
|
ce17426063 | ||
|
8a6e4c910d | ||
|
5ba0497788 | ||
|
2f21f27da9 | ||
|
8eb55f2808 | ||
|
b166ca8d61 |
51
.github/workflows/go.yml
vendored
51
.github/workflows/go.yml
vendored
@ -68,12 +68,38 @@ jobs:
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Fido2
|
||||
run: brew install libfido2
|
||||
- name: Build
|
||||
run: go build -tags "nofido2" -o "goldwarden_macos_x86_64" -v .
|
||||
run: go build -o "goldwarden_macos_x86_64" -v .
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: goldwarden-macos_x86_64
|
||||
path: ./goldwarden_macos_x86_64
|
||||
|
||||
build_macos_aarch64:
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Fido2
|
||||
run: brew install libfido2
|
||||
- name: Build
|
||||
run: go build -o "goldwarden_macos_aarch64" -v .
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: goldwarden-macos_aarch64
|
||||
path: ./goldwarden_macos_aarch64
|
||||
|
||||
build_windows_x86_64:
|
||||
runs-on: windows-latest
|
||||
@ -84,9 +110,30 @@ jobs:
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: MinoruSekine/setup-scoop@v2
|
||||
- name: Fido2
|
||||
run: |
|
||||
scoop bucket add keys.pub https://github.com/keys-pub/scoop-bucket
|
||||
scoop install libfido2
|
||||
- name: Build
|
||||
run: go build -tags "nofido2" -o "goldwarden_windows_x86_64.exe" -v .
|
||||
run: go build -o "goldwarden_windows_x86_64.exe" -v .
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: goldwarden-windows_x86_64.exe
|
||||
path: ./goldwarden_windows_x86_64.exe
|
||||
|
||||
build_windows_aarch64:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- name: Build
|
||||
run: set GOARCH=arm64 && go build -tags nofido2 -o "goldwarden_windows_aarch64.exe" -v .
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: goldwarden-windows_aarch64.exe
|
||||
path: ./goldwarden_windows_aarch64.exe
|
||||
|
57
.github/workflows/release.yml
vendored
57
.github/workflows/release.yml
vendored
@ -39,12 +39,6 @@ jobs:
|
||||
with:
|
||||
files: './goldwarden_linux_x86_64'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Validate ArchLinux PKGBUILD
|
||||
uses: hapakaien/archlinux-package-action@v2
|
||||
with:
|
||||
flags: ''
|
||||
namcap: true
|
||||
updpkgsums: true
|
||||
- name: Publish AUR package
|
||||
uses: KSXGitHub/github-actions-deploy-aur@v2.7.0
|
||||
with:
|
||||
@ -135,13 +129,39 @@ jobs:
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Fido2
|
||||
run: brew install libfido2
|
||||
- name: Build
|
||||
run: go build -tags "nofido2" -o "goldwarden_macos_x86_64" -v .
|
||||
run: go build -o "goldwarden_macos_x86_64" -v .
|
||||
- uses: AButler/upload-release-assets@v2.0
|
||||
with:
|
||||
files: './goldwarden_macos_x86_64'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build_macos_aarch64:
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Fido2
|
||||
run: brew install libfido2
|
||||
- name: Build
|
||||
run: go build -o "goldwarden_macos_aarch64" -v .
|
||||
- uses: AButler/upload-release-assets@v2.0
|
||||
with:
|
||||
files: './goldwarden_macos_aarch64'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build_windows_x86_64:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
@ -151,9 +171,30 @@ jobs:
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- uses: MinoruSekine/setup-scoop@v2
|
||||
- name: Fido2
|
||||
run: |
|
||||
scoop bucket add keys.pub https://github.com/keys-pub/scoop-bucket
|
||||
scoop install libfido2
|
||||
- name: Build
|
||||
run: go build -tags "nofido2" -o "goldwarden_windows_x86_64.exe" -v .
|
||||
run: go build -o "goldwarden_windows_x86_64.exe" -v .
|
||||
- uses: AButler/upload-release-assets@v2.0
|
||||
with:
|
||||
files: './goldwarden_windows_x86_64.exe'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build_windows_aarch64:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- name: Build
|
||||
run: set GOARCH=arm64 && go build -tags nofido2 -o "goldwarden_windows_aarch64.exe" -v .
|
||||
- uses: AButler/upload-release-assets@v2.0
|
||||
with:
|
||||
files: './goldwarden_windows_aarch64.exe'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,4 +3,5 @@ goldwarden
|
||||
__pycache__
|
||||
.flatpak-builder
|
||||
flatpak-pip-generator
|
||||
repo
|
||||
repo
|
||||
__debug*
|
2
PKGBUILD
2
PKGBUILD
@ -1,5 +1,5 @@
|
||||
pkgname=goldwarden
|
||||
pkgver=0.2.10
|
||||
pkgver=0.2.12
|
||||
pkgrel=1
|
||||
pkgdesc='A feature-packed Bitwarden compatible desktop integration'
|
||||
arch=('x86_64' 'aarch64')
|
||||
|
11
Readme.md
11
Readme.md
@ -4,8 +4,8 @@ Goldwarden is a Bitwarden compatible desktop client. It focuses on providing use
|
||||
do not (yet) have or are not willing to add, and enhanced security measures that other tools do not provide, such as:
|
||||
|
||||
- Support for SSH Agent (Git signing and SSH login)
|
||||
- System wide autotype (Gnome, KDE only for now)
|
||||
- Biometric authentication (via Polkit) for each credential access
|
||||
- System wide autotype (Linux - Gnome, KDE only for now)
|
||||
- Biometric authentication
|
||||
- Implements Bitwarden browser-extension biometrics on Linux
|
||||
- Support for injecting environment variables into the environment of a cli command
|
||||
- Vault content is held encrypted in memory and only briefly decrypted when needed
|
||||
@ -56,3 +56,10 @@ go install github.com/quexten/goldwarden@latest
|
||||
### Setup and Usage
|
||||
To get started, follow the instructions provided in the wiki https://github.com/quexten/goldwarden/wiki/Getting-Started.
|
||||
For instructions on specific features, also consult the wiki page for the feature.
|
||||
|
||||
### Contributing
|
||||
Interested in contributing a feature or bug-fix? Great! Here is some information on how to set up your development environment:
|
||||
|
||||
https://github.com/quexten/goldwarden/wiki/Setting-up-the-Development-Environment
|
||||
|
||||
After that, create a PR. If you encounter any issues, feel free to open a discussion thread.
|
||||
|
@ -103,7 +103,7 @@ func ensureIsNotLocked(action Action) Action {
|
||||
}
|
||||
}
|
||||
|
||||
systemauth.CreatePinSession(*ctx)
|
||||
systemauth.CreatePinSession(*ctx, systemauth.SSHTTL)
|
||||
}
|
||||
|
||||
return action(request, cfg, vault, ctx)
|
||||
|
@ -1,6 +1,10 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/quexten/goldwarden/agent/config"
|
||||
"github.com/quexten/goldwarden/agent/sockets"
|
||||
"github.com/quexten/goldwarden/agent/vault"
|
||||
@ -55,6 +59,89 @@ func handleSetNotifications(request messages.IPCMessage, cfg *config.Config, vau
|
||||
})
|
||||
}
|
||||
|
||||
func handleSetVaultURL(request messages.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (response messages.IPCMessage, err error) {
|
||||
vaultURL := messages.ParsePayload(request).(messages.SetVaultURLRequest).Value
|
||||
cfg.ConfigFile.VaultUrl = vaultURL
|
||||
err = cfg.WriteConfig()
|
||||
if err != nil {
|
||||
return messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||
Success: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
|
||||
type ConfigResponse struct {
|
||||
Version string `json:"version"`
|
||||
GitHash string `json:"gitHash"`
|
||||
Environment struct {
|
||||
Vault string `json:"vault"`
|
||||
Api string `json:"api"`
|
||||
Identity string `json:"identity"`
|
||||
Notifications string `json:"notifications"`
|
||||
}
|
||||
}
|
||||
|
||||
func handleSetURLsAutomatically(request messages.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (response messages.IPCMessage, err error) {
|
||||
autoconfigBaseURL := messages.ParsePayload(request).(messages.SetURLsAutomaticallyRequest).Value
|
||||
|
||||
// make http request
|
||||
resp, err := http.Get(autoconfigBaseURL + "/api/config")
|
||||
if err != nil {
|
||||
return messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||
Success: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// parse response
|
||||
var configResponse ConfigResponse
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||
Success: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
err = json.Unmarshal(body, &configResponse)
|
||||
if err != nil {
|
||||
return messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||
Success: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
cfg.ConfigFile.ApiUrl = configResponse.Environment.Api
|
||||
cfg.ConfigFile.IdentityUrl = configResponse.Environment.Identity
|
||||
cfg.ConfigFile.NotificationsUrl = configResponse.Environment.Notifications
|
||||
cfg.ConfigFile.VaultUrl = configResponse.Environment.Vault
|
||||
|
||||
err = cfg.WriteConfig()
|
||||
if err != nil {
|
||||
return messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||
Success: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetConfigEnvironment(request messages.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (response messages.IPCMessage, err error) {
|
||||
return messages.IPCMessageFromPayload(messages.GetConfigEnvironmentResponse{
|
||||
ApiURL: cfg.ConfigFile.ApiUrl,
|
||||
IdentityURL: cfg.ConfigFile.IdentityUrl,
|
||||
NotificationsURL: cfg.ConfigFile.NotificationsUrl,
|
||||
VaultURL: cfg.ConfigFile.VaultUrl,
|
||||
})
|
||||
}
|
||||
|
||||
func handleSetClientID(request messages.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (response messages.IPCMessage, err error) {
|
||||
clientID := messages.ParsePayload(request).(messages.SetClientIDRequest).Value
|
||||
cfg.SetClientID(clientID)
|
||||
@ -99,6 +186,9 @@ func init() {
|
||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.SetIdentityURLRequest{}), handleSetIdentity)
|
||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.SetApiURLRequest{}), handleSetApiURL)
|
||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.SetNotificationsURLRequest{}), handleSetNotifications)
|
||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.SetVaultURLRequest{}), handleSetVaultURL)
|
||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.SetURLsAutomaticallyRequest{}), handleSetURLsAutomatically)
|
||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.GetConfigEnvironmentRequest{}), handleGetConfigEnvironment)
|
||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.GetRuntimeConfigRequest{}), handleGetRuntimeConfig)
|
||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.SetClientIDRequest{}), handleSetClientID)
|
||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.SetClientSecretRequest{}), handleSetClientSecret)
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
func handleLogin(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext *sockets.CallingContext) (response messages.IPCMessage, err error) {
|
||||
if !cfg.HasPin() && !cfg.ConfigFile.RuntimeConfig.DisablePinRequirement {
|
||||
if !cfg.HasPin() {
|
||||
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||
Success: false,
|
||||
Message: "No pin set. Set a pin first!",
|
||||
|
@ -3,10 +3,7 @@ package actions
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
||||
"github.com/quexten/goldwarden/agent/config"
|
||||
"github.com/quexten/goldwarden/agent/sockets"
|
||||
@ -72,9 +69,8 @@ func handleGetLoginCipher(request messages.IPCMessage, cfg *config.Config, vault
|
||||
if !login.Login.Totp.IsNull() {
|
||||
decryptedTotp, err := crypto.DecryptWith(login.Login.Totp, cipherKey)
|
||||
if err == nil {
|
||||
code, err := totp.GenerateCode(string(strings.ReplaceAll(string(decryptedTotp), " ", "")), time.Now())
|
||||
if err == nil {
|
||||
decryptedLogin.TwoFactorCode = code
|
||||
decryptedLogin.TOTPSeed = string(decryptedTotp)
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
}
|
||||
@ -101,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("Approve List Credentials", 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)
|
||||
@ -124,6 +120,8 @@ func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, va
|
||||
var decryptedName []byte = []byte{}
|
||||
var decryptedUsername []byte = []byte{}
|
||||
var decryptedPassword []byte = []byte{}
|
||||
var decryptedTotp []byte = []byte{}
|
||||
var decryptedURL []byte = []byte{}
|
||||
|
||||
if !login.Name.IsNull() {
|
||||
decryptedName, err = crypto.DecryptWith(login.Name, key)
|
||||
@ -131,6 +129,8 @@ func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, va
|
||||
actionsLog.Warn("Could not decrypt login:" + err.Error())
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
decryptedName = []byte{}
|
||||
}
|
||||
|
||||
if !login.Login.Username.IsNull() {
|
||||
@ -139,6 +139,8 @@ func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, va
|
||||
actionsLog.Warn("Could not decrypt login:" + err.Error())
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
decryptedUsername = []byte{}
|
||||
}
|
||||
|
||||
if !login.Login.Password.IsNull() {
|
||||
@ -147,6 +149,28 @@ func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, va
|
||||
actionsLog.Warn("Could not decrypt login:" + err.Error())
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
decryptedPassword = []byte{}
|
||||
}
|
||||
|
||||
if !login.Login.Totp.IsNull() {
|
||||
decryptedTotp, err = crypto.DecryptWith(login.Login.Totp, key)
|
||||
if err != nil {
|
||||
actionsLog.Warn("Could not decrypt login:" + err.Error())
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
decryptedTotp = []byte{}
|
||||
}
|
||||
|
||||
if !login.Login.URI.IsNull() {
|
||||
decryptedURL, err = crypto.DecryptWith(login.Login.URI, key)
|
||||
if err != nil {
|
||||
actionsLog.Warn("Could not decrypt login:" + err.Error())
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
decryptedURL = []byte{}
|
||||
}
|
||||
|
||||
decryptedLoginCiphers = append(decryptedLoginCiphers, messages.DecryptedLoginCipher{
|
||||
@ -154,6 +178,8 @@ func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, va
|
||||
Username: string(decryptedUsername),
|
||||
UUID: login.ID.String(),
|
||||
Password: string(decryptedPassword),
|
||||
TOTPSeed: string(decryptedTotp),
|
||||
URI: string(decryptedURL),
|
||||
})
|
||||
|
||||
// prevent deadlock from enclaves
|
||||
|
32
agent/actions/send.go
Normal file
32
agent/actions/send.go
Normal file
@ -0,0 +1,32 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/quexten/goldwarden/agent/bitwarden"
|
||||
"github.com/quexten/goldwarden/agent/config"
|
||||
"github.com/quexten/goldwarden/agent/sockets"
|
||||
"github.com/quexten/goldwarden/agent/vault"
|
||||
"github.com/quexten/goldwarden/ipc/messages"
|
||||
)
|
||||
|
||||
func handleCreateSend(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext *sockets.CallingContext) (response messages.IPCMessage, err error) {
|
||||
token, err := cfg.GetToken()
|
||||
if err != nil {
|
||||
return messages.IPCMessage{}, fmt.Errorf("error getting token: %w", err)
|
||||
}
|
||||
parsedMsg := messages.ParsePayload(msg).(messages.CreateSendRequest)
|
||||
|
||||
ctx := context.WithValue(context.TODO(), bitwarden.AuthToken{}, token.AccessToken)
|
||||
url, err := bitwarden.CreateSend(ctx, cfg, vault, parsedMsg.Name, parsedMsg.Text)
|
||||
|
||||
response, err = messages.IPCMessageFromPayload(messages.CreateSendResponse{
|
||||
URL: url,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.CreateSendRequest{}), ensureIsNotLocked(ensureIsLoggedIn(handleCreateSend)))
|
||||
}
|
@ -218,6 +218,20 @@ func EncryptWith(data []byte, encType EncStringType, key SymmetricEncryptionKey)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func EncryptWithToString(data []byte, encType EncStringType, key SymmetricEncryptionKey) (string, error) {
|
||||
s, err := EncryptWith(data, encType, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
marshalled, err := s.MarshalText()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(marshalled), nil
|
||||
}
|
||||
|
||||
func GenerateAsymmetric(useMemguard bool) (AsymmetricEncryptionKey, error) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
|
@ -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
|
||||
|
111
agent/bitwarden/send.go
Normal file
111
agent/bitwarden/send.go
Normal file
@ -0,0 +1,111 @@
|
||||
package bitwarden
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
||||
"github.com/quexten/goldwarden/agent/config"
|
||||
"github.com/quexten/goldwarden/agent/vault"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
type SendFileMetadata struct {
|
||||
FileName string `json:"fileName"`
|
||||
Id string `json:"id"`
|
||||
Size int `json:"size"`
|
||||
SizeName string `json:"sizeName"`
|
||||
}
|
||||
|
||||
type SendTextMetadata struct {
|
||||
Hidden bool `json:"hidden"`
|
||||
Response *string `json:"response"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type SendMetadata struct {
|
||||
CreatorIdentifier string
|
||||
ExpirationDate string
|
||||
File SendFileMetadata
|
||||
Id string
|
||||
Name string
|
||||
Object string
|
||||
Text SendTextMetadata
|
||||
Type int
|
||||
}
|
||||
|
||||
type SendCreateRequest struct {
|
||||
AccessCount *int `json:"accessCount"`
|
||||
AccessId *string `json:"accessId"`
|
||||
DeletionDate string `json:"deletionDate"`
|
||||
Disabled bool `json:"disabled"`
|
||||
ExpirationDate *string `json:"expirationDate"`
|
||||
HideEmail bool `json:"hideEmail"`
|
||||
Key string `json:"key"`
|
||||
MaxAccessCount *int `json:"maxAccessCount"`
|
||||
Name string `json:"name"`
|
||||
Notes *string `json:"notes"`
|
||||
Text SendTextMetadata `json:"text"`
|
||||
Type int `json:"type"`
|
||||
}
|
||||
|
||||
func CreateSend(ctx context.Context, cfg *config.Config, vault *vault.Vault, name string, text string) (string, error) {
|
||||
timestampIn14Days := time.Now().AddDate(0, 0, 14)
|
||||
timestampIn14DaysStr := timestampIn14Days.Format("2006-01-02T15:04:05Z")
|
||||
|
||||
// generate 32 byte key
|
||||
sendSourceKey := make([]byte, 32)
|
||||
_, err := io.ReadFull(rand.Reader, sendSourceKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
encryptedSendSourceKey, err := crypto.EncryptWithToString(sendSourceKey, crypto.AesCbc256_HmacSha256_B64, vault.Keyring.GetAccountKey())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sendUseKeyPairBytes := make([]byte, 64)
|
||||
hkdf.New(sha256.New, sendSourceKey, []byte("bitwarden-send"), []byte("send")).Read(sendUseKeyPairBytes)
|
||||
|
||||
sendUseKeyPair, err := crypto.MemorySymmetricEncryptionKeyFromBytes(sendUseKeyPairBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
encryptedName, err := crypto.EncryptWithToString([]byte(name), crypto.AesCbc256_HmacSha256_B64, sendUseKeyPair)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
encryptedText, err := crypto.EncryptWithToString([]byte(text), crypto.AesCbc256_HmacSha256_B64, sendUseKeyPair)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sendRequest := SendCreateRequest{
|
||||
DeletionDate: timestampIn14DaysStr,
|
||||
Disabled: false,
|
||||
HideEmail: false,
|
||||
Key: encryptedSendSourceKey,
|
||||
Name: encryptedName,
|
||||
Text: SendTextMetadata{
|
||||
Hidden: false,
|
||||
Text: encryptedText,
|
||||
},
|
||||
Type: 0,
|
||||
}
|
||||
|
||||
var result SendCreateRequest
|
||||
err = authenticatedHTTPPost(ctx, cfg.ConfigFile.ApiUrl+"/sends", &result, sendRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cfg.ConfigFile.VaultUrl + "/#/send/" + *result.AccessId + "/" + strings.ReplaceAll(base64.RawURLEncoding.EncodeToString(sendSourceKey), "+", "-"), nil
|
||||
}
|
@ -10,7 +10,6 @@ import (
|
||||
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
||||
"github.com/quexten/goldwarden/agent/bitwarden/models"
|
||||
"github.com/quexten/goldwarden/agent/config"
|
||||
"github.com/quexten/goldwarden/agent/notify"
|
||||
"github.com/quexten/goldwarden/agent/vault"
|
||||
"github.com/quexten/goldwarden/logging"
|
||||
)
|
||||
@ -35,7 +34,6 @@ func DoFullSync(ctx context.Context, vault *vault.Vault, config *config.Config,
|
||||
sync, err := Sync(ctx, config)
|
||||
if err != nil {
|
||||
log.Error("Could not sync: %v", err)
|
||||
notify.Notify("Goldwarden", "Could not sync", "", 0, func() {})
|
||||
if allowCache {
|
||||
home, _ := os.UserHomeDir()
|
||||
sync, err = ReadVault(home + path)
|
||||
|
@ -5,13 +5,13 @@ import (
|
||||
"context"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/quexten/goldwarden/agent/bitwarden/models"
|
||||
"github.com/quexten/goldwarden/agent/config"
|
||||
"github.com/quexten/goldwarden/agent/notify"
|
||||
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
|
||||
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
|
||||
"github.com/quexten/goldwarden/agent/vault"
|
||||
@ -70,9 +70,6 @@ func RunWebsocketDaemon(ctx context.Context, vault *vault.Vault, cfg *config.Con
|
||||
}
|
||||
|
||||
func connectToWebsocket(ctx context.Context, vault *vault.Vault, cfg *config.Config) error {
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
|
||||
url, err := url.Parse(cfg.ConfigFile.NotificationsUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -200,20 +197,23 @@ func connectToWebsocket(ctx context.Context, vault *vault.Vault, cfg *config.Con
|
||||
}
|
||||
websocketLog.Info("AuthRequest details " + authRequest.RequestIpAddress + " " + authRequest.RequestDeviceType)
|
||||
|
||||
var message = "Do you want to allow " + authRequest.RequestIpAddress + " (" + authRequest.RequestDeviceType + ") to login to your account?"
|
||||
if approved, err := pinentry.GetApproval("Paswordless Login Request", message); err != nil || !approved {
|
||||
websocketLog.Info("AuthRequest denied")
|
||||
break
|
||||
}
|
||||
if !biometrics.CheckBiometrics(biometrics.AccessVault) {
|
||||
websocketLog.Info("AuthRequest denied - biometrics required")
|
||||
break
|
||||
}
|
||||
notify.Notify("Passwordless Login Request", authRequest.RequestIpAddress+" - "+authRequest.RequestDeviceType, "", 0, func() {
|
||||
var message = "Do you want to allow " + authRequest.RequestIpAddress + " (" + authRequest.RequestDeviceType + ") to login to your account?"
|
||||
if approved, err := pinentry.GetApproval("Paswordless Login Request", message); err != nil || !approved {
|
||||
websocketLog.Info("AuthRequest denied")
|
||||
return
|
||||
}
|
||||
if !biometrics.CheckBiometrics(biometrics.AccessVault) {
|
||||
websocketLog.Info("AuthRequest denied - biometrics required")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = CreateAuthResponse(context.WithValue(ctx, AuthToken{}, token.AccessToken), authRequest, vault.Keyring, cfg)
|
||||
if err != nil {
|
||||
websocketLog.Error("Error creating auth response %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
_, err = CreateAuthResponse(context.WithValue(ctx, AuthToken{}, token.AccessToken), authRequest, vault.Keyring, cfg)
|
||||
if err != nil {
|
||||
websocketLog.Error("Error creating auth response %s", err)
|
||||
}
|
||||
break
|
||||
case AuthRequestResponse:
|
||||
websocketLog.Info("AuthRequestResponse received")
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
||||
"github.com/quexten/goldwarden/agent/notify"
|
||||
"github.com/quexten/goldwarden/agent/pincache"
|
||||
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
|
||||
"github.com/quexten/goldwarden/agent/vault"
|
||||
"github.com/tink-crypto/tink-go/v2/aead/subtle"
|
||||
@ -31,30 +32,26 @@ const (
|
||||
)
|
||||
|
||||
type RuntimeConfig struct {
|
||||
DisableAuth bool
|
||||
DisablePinRequirement bool
|
||||
AuthMethod string
|
||||
DoNotPersistConfig bool
|
||||
ConfigDirectory string
|
||||
DisableSSHAgent bool
|
||||
WebsocketDisabled bool
|
||||
ApiURI string
|
||||
IdentityURI string
|
||||
NotificationsURI string
|
||||
SingleProcess bool
|
||||
DeviceUUID string
|
||||
User string
|
||||
Password string
|
||||
Pin string
|
||||
UseMemguard bool
|
||||
SSHAgentSocketPath string
|
||||
GoldwardenSocketPath string
|
||||
AuthMethod string
|
||||
DoNotPersistConfig bool
|
||||
ConfigDirectory string
|
||||
DisableSSHAgent bool
|
||||
WebsocketDisabled bool
|
||||
DeviceUUID string
|
||||
User string
|
||||
Password string
|
||||
Pin string
|
||||
UseMemguard bool
|
||||
SSHAgentSocketPath string
|
||||
GoldwardenSocketPath string
|
||||
DaemonAuthToken string
|
||||
}
|
||||
|
||||
type ConfigFile struct {
|
||||
IdentityUrl string
|
||||
ApiUrl string
|
||||
NotificationsUrl string
|
||||
VaultUrl string
|
||||
EncryptedClientID string
|
||||
EncryptedClientSecret string
|
||||
DeviceUUID string
|
||||
@ -91,6 +88,7 @@ func DefaultConfig(useMemguard bool) Config {
|
||||
IdentityUrl: "https://vault.bitwarden.com/identity",
|
||||
ApiUrl: "https://vault.bitwarden.com/api",
|
||||
NotificationsUrl: "https://notifications.bitwarden.com",
|
||||
VaultUrl: "https://vault.bitwarden.com",
|
||||
EncryptedClientID: "",
|
||||
EncryptedClientSecret: "",
|
||||
DeviceUUID: deviceUUID.String(),
|
||||
@ -133,6 +131,7 @@ func (c *Config) Unlock(password string) bool {
|
||||
keyBuffer := NewBufferFromBytes(key, c.useMemguard)
|
||||
c.key = &keyBuffer
|
||||
notify.Notify("Goldwarden", "Vault Unlocked", "", 60*time.Second, func() {})
|
||||
pincache.SetPin(c.useMemguard, []byte(password))
|
||||
return true
|
||||
}
|
||||
|
||||
@ -221,6 +220,8 @@ func (c *Config) UpdatePin(password string, write bool) {
|
||||
if write {
|
||||
c.WriteConfig()
|
||||
}
|
||||
|
||||
pincache.SetPin(c.useMemguard, []byte(password))
|
||||
}
|
||||
|
||||
func (c *Config) GetToken() (LoginToken, error) {
|
||||
@ -469,7 +470,10 @@ func (config *Config) WriteConfig() error {
|
||||
os.Remove(config.ConfigFile.RuntimeConfig.ConfigDirectory)
|
||||
parentDirectory := config.ConfigFile.RuntimeConfig.ConfigDirectory[:len(config.ConfigFile.RuntimeConfig.ConfigDirectory)-len("/goldwarden.json")]
|
||||
if _, err := os.Stat(parentDirectory); os.IsNotExist(err) {
|
||||
os.Mkdir(parentDirectory, 0700)
|
||||
err := os.MkdirAll(parentDirectory, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(config.ConfigFile.RuntimeConfig.ConfigDirectory, os.O_CREATE|os.O_WRONLY, 0600)
|
||||
@ -536,10 +540,21 @@ func ReadConfig(rtCfg RuntimeConfig) (Config, error) {
|
||||
}
|
||||
|
||||
func (cfg *Config) TryUnlock(vault *vault.Vault) error {
|
||||
pin, err := pinentry.GetPassword("Unlock Goldwarden", "Enter the vault PIN")
|
||||
if err != nil {
|
||||
return err
|
||||
var pin string
|
||||
if pincache.HasPin() {
|
||||
pinBytes, err := pincache.GetPin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pin = string(pinBytes)
|
||||
} else {
|
||||
var err error
|
||||
pin, err = pinentry.GetPassword("Unlock Goldwarden", "Enter the vault PIN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
success := cfg.Unlock(pin)
|
||||
if !success {
|
||||
return errors.New("invalid PIN")
|
||||
|
@ -1,11 +1,18 @@
|
||||
//go:build windows || darwin
|
||||
//go:build darwin
|
||||
|
||||
package notify
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gen2brain/beeep"
|
||||
)
|
||||
|
||||
func Notify(title string, body string, actionName string, timeout time.Duration, onclose func()) error {
|
||||
// no notifications on windows or darwin
|
||||
err := beeep.Notify(title, body, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
//go:build linux || freebsd
|
||||
//go:build freebsd || linux
|
||||
|
||||
package notify
|
||||
|
||||
|
24
agent/notify/windows.go
Normal file
24
agent/notify/windows.go
Normal file
@ -0,0 +1,24 @@
|
||||
//go:build windows
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-toast/toast"
|
||||
)
|
||||
|
||||
func Notify(title string, body string, actionName string, timeout time.Duration, onclose func()) error {
|
||||
notification := toast.Notification{
|
||||
AppID: "Goldwarden",
|
||||
Title: title,
|
||||
Message: body,
|
||||
Audio: toast.Silent,
|
||||
}
|
||||
|
||||
return notification.Push()
|
||||
}
|
||||
|
||||
func ListenForNotifications() error {
|
||||
return nil
|
||||
}
|
41
agent/pincache/pincache.go
Normal file
41
agent/pincache/pincache.go
Normal file
@ -0,0 +1,41 @@
|
||||
package pincache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
|
||||
)
|
||||
|
||||
var cachedPin *memguard.Enclave
|
||||
|
||||
func SetPin(useMemguard bool, pin []byte) {
|
||||
cachedPin = memguard.NewEnclave(pin)
|
||||
}
|
||||
|
||||
func GetPin() ([]byte, error) {
|
||||
approved := biometrics.CheckBiometrics(biometrics.SSHKey)
|
||||
if approved {
|
||||
bufer, err := cachedPin.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bufer.Bytes(), nil
|
||||
} else {
|
||||
return nil, errors.New("biometrics not approved")
|
||||
}
|
||||
}
|
||||
|
||||
func HasPin() bool {
|
||||
return cachedPin != nil
|
||||
}
|
||||
|
||||
func ClearPin() {
|
||||
pin, err := cachedPin.Open()
|
||||
if err != nil {
|
||||
cachedPin = nil
|
||||
return
|
||||
}
|
||||
pin.Destroy()
|
||||
cachedPin = nil
|
||||
}
|
6
agent/processsecurity/isdelve/isdelve.go
Normal file
6
agent/processsecurity/isdelve/isdelve.go
Normal file
@ -0,0 +1,6 @@
|
||||
//go:build delve
|
||||
// +build delve
|
||||
|
||||
package isdelve
|
||||
|
||||
const Enabled = true
|
6
agent/processsecurity/isdelve/nodelve.go
Normal file
6
agent/processsecurity/isdelve/nodelve.go
Normal file
@ -0,0 +1,6 @@
|
||||
//go:build !delve
|
||||
// +build !delve
|
||||
|
||||
package isdelve
|
||||
|
||||
const Enabled = false
|
@ -6,11 +6,18 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/quexten/goldwarden/agent/processsecurity/isdelve"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const IDLE_TIME = 60 * 15
|
||||
|
||||
func DisableDumpable() error {
|
||||
return unix.Prctl(unix.PR_SET_DUMPABLE, 0, 0, 0, 0)
|
||||
if isdelve.Enabled {
|
||||
return nil
|
||||
} else {
|
||||
return unix.Prctl(unix.PR_SET_DUMPABLE, 0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func MonitorLocks(onlock func()) error {
|
||||
@ -70,7 +77,7 @@ func MonitorIdle(onidle func()) error {
|
||||
return err
|
||||
}
|
||||
secondsIdle := res / 1000
|
||||
if secondsIdle > 60*1 {
|
||||
if secondsIdle > IDLE_TIME {
|
||||
if !wasidle {
|
||||
wasidle = true
|
||||
onidle()
|
||||
|
@ -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
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/quexten/goldwarden/agent/config"
|
||||
@ -42,7 +40,7 @@ func (vaultAgent vaultAgent) List() ([]*agent.Key, error) {
|
||||
return nil, errors.New("vault is locked")
|
||||
}
|
||||
|
||||
systemauth.CreatePinSession(vaultAgent.context)
|
||||
systemauth.CreatePinSession(vaultAgent.context, systemauth.SSHTTL)
|
||||
}
|
||||
|
||||
vaultSSHKeys := (*vaultAgent.vault).GetSSHKeys()
|
||||
@ -89,7 +87,7 @@ func (vaultAgent vaultAgent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signatur
|
||||
return nil, errors.New("vault is locked")
|
||||
}
|
||||
|
||||
systemauth.CreatePinSession(vaultAgent.context)
|
||||
systemauth.CreatePinSession(vaultAgent.context, systemauth.SSHTTL)
|
||||
}
|
||||
|
||||
var signer ssh.Signer
|
||||
@ -132,14 +130,23 @@ func (vaultAgent vaultAgent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signatur
|
||||
message = fmt.Sprintf(requestTemplate, vaultAgent.context.UserName, sshKey.Name)
|
||||
}
|
||||
|
||||
if approved, err := pinentry.GetApproval("SSH Key Signing Request", message); err != nil || !approved {
|
||||
log.Info("Sign Request for key: %s denied", sshKey.Name)
|
||||
return nil, errors.New("Approval not given")
|
||||
}
|
||||
// todo refactor
|
||||
if !systemauth.GetSSHSession(vaultAgent.context) {
|
||||
if approved, err := pinentry.GetApproval("SSH Key Signing Request", message); err != nil || !approved {
|
||||
log.Info("Sign Request for key: %s denied", sshKey.Name)
|
||||
return nil, errors.New("Approval not given")
|
||||
}
|
||||
|
||||
if permission, err := systemauth.GetPermission(systemauth.SSHKey, vaultAgent.context, vaultAgent.config); err != nil || !permission {
|
||||
log.Info("Sign Request for key: %s denied", key.Marshal())
|
||||
return nil, errors.New("Biometrics not checked")
|
||||
if !systemauth.VerifyPinSession(vaultAgent.context) {
|
||||
if permission, err := systemauth.GetPermission(systemauth.SSHKey, vaultAgent.context, vaultAgent.config); err != nil || !permission {
|
||||
log.Info("Sign Request for key: %s denied", key.Marshal())
|
||||
return nil, errors.New("Biometrics not checked")
|
||||
}
|
||||
}
|
||||
|
||||
systemauth.CreateSSHSession(vaultAgent.context)
|
||||
} else {
|
||||
log.Info("Using cached session approval")
|
||||
}
|
||||
|
||||
var rand = rand.Reader
|
||||
@ -184,38 +191,3 @@ func NewVaultAgent(vault *vault.Vault, config *config.Config, runtimeConfig *con
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v SSHAgentServer) Serve() {
|
||||
path := v.runtimeConfig.SSHAgentSocketPath
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
if err := os.Remove(path); err != nil {
|
||||
log.Error("Could not remove old socket file: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
listener, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Info("SSH Agent listening on %s", path)
|
||||
|
||||
for {
|
||||
var conn, err = listener.Accept()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
callingContext := sockets.GetCallingContext(conn)
|
||||
|
||||
log.Info("SSH Agent connection from %s>%s>%s \nby user %s", callingContext.GrandParentProcessName, callingContext.ParentProcessName, callingContext.ProcessName, callingContext.UserName)
|
||||
log.Info("SSH Agent connection accepted")
|
||||
|
||||
go agent.ServeAgent(vaultAgent{
|
||||
vault: v.vault,
|
||||
config: v.config,
|
||||
unlockRequestAction: v.unlockRequestAction,
|
||||
context: callingContext,
|
||||
}, conn)
|
||||
}
|
||||
}
|
||||
|
47
agent/ssh/sshsocketunix.go
Normal file
47
agent/ssh/sshsocketunix.go
Normal file
@ -0,0 +1,47 @@
|
||||
//go:build !windows
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/quexten/goldwarden/agent/sockets"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
func (v SSHAgentServer) Serve() {
|
||||
path := v.runtimeConfig.SSHAgentSocketPath
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
if err := os.Remove(path); err != nil {
|
||||
log.Error("Could not remove old socket file: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
listener, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
log.Info("SSH Agent listening on %s", path)
|
||||
|
||||
for {
|
||||
var conn, err = listener.Accept()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
callingContext := sockets.GetCallingContext(conn)
|
||||
|
||||
log.Info("SSH Agent connection from %s>%s>%s \nby user %s", callingContext.GrandParentProcessName, callingContext.ParentProcessName, callingContext.ProcessName, callingContext.UserName)
|
||||
log.Info("SSH Agent connection accepted")
|
||||
|
||||
go agent.ServeAgent(vaultAgent{
|
||||
vault: v.vault,
|
||||
config: v.config,
|
||||
unlockRequestAction: v.unlockRequestAction,
|
||||
context: callingContext,
|
||||
}, conn)
|
||||
}
|
||||
}
|
39
agent/ssh/sshsocketwindows.go
Normal file
39
agent/ssh/sshsocketwindows.go
Normal file
@ -0,0 +1,39 @@
|
||||
//go:build windows
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/go-winio"
|
||||
"github.com/quexten/goldwarden/agent/sockets"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
func (v SSHAgentServer) Serve() {
|
||||
pipePath := `\\.\pipe\openssh-ssh-agent`
|
||||
|
||||
l, err := winio.ListenPipe(pipePath, nil)
|
||||
if err != nil {
|
||||
log.Fatal("listen error:", err)
|
||||
}
|
||||
defer l.Close()
|
||||
log.Info("Server listening on named pipe %v\n", pipePath)
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("accept error:", err)
|
||||
}
|
||||
|
||||
callingContext := sockets.GetCallingContext(conn)
|
||||
|
||||
log.Info("SSH Agent connection from %s>%s>%s \nby user %s", callingContext.GrandParentProcessName, callingContext.ParentProcessName, callingContext.ProcessName, callingContext.UserName)
|
||||
log.Info("SSH Agent connection accepted")
|
||||
|
||||
go agent.ServeAgent(vaultAgent{
|
||||
vault: v.vault,
|
||||
config: v.config,
|
||||
unlockRequestAction: v.unlockRequestAction,
|
||||
context: callingContext,
|
||||
}, conn)
|
||||
}
|
||||
}
|
@ -1,21 +1,11 @@
|
||||
package biometrics
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/quexten/goldwarden/logging"
|
||||
)
|
||||
|
||||
var log = logging.GetLogger("Goldwarden", "Biometrics")
|
||||
|
||||
var biometricsDisabled = false
|
||||
|
||||
func init() {
|
||||
if os.Getenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED") == "true" {
|
||||
biometricsDisabled = true
|
||||
}
|
||||
}
|
||||
|
||||
type Approval string
|
||||
|
||||
const (
|
||||
|
@ -43,10 +43,6 @@ const POLICY = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
</policyconfig>`
|
||||
|
||||
func CheckBiometrics(approvalType Approval) bool {
|
||||
if biometricsDisabled {
|
||||
return true
|
||||
}
|
||||
|
||||
log.Info("Checking biometrics for %s", approvalType.String())
|
||||
|
||||
authority, err := polkit.NewAuthority()
|
||||
@ -84,10 +80,6 @@ func CheckBiometrics(approvalType Approval) bool {
|
||||
}
|
||||
|
||||
func BiometricsWorking() bool {
|
||||
if biometricsDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
authority, err := polkit.NewAuthority()
|
||||
if err != nil {
|
||||
log.Warn("Failed to create polkit authority: %s", err.Error())
|
||||
|
@ -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
|
||||
}
|
||||
|
50
agent/systemauth/pinentry/keybase-pinentry.go
Normal file
50
agent/systemauth/pinentry/keybase-pinentry.go
Normal file
@ -0,0 +1,50 @@
|
||||
//go:build windows || darwin
|
||||
|
||||
package pinentry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/keybase/client/go/logger"
|
||||
"github.com/keybase/client/go/protocol/keybase1"
|
||||
pinentry "github.com/quexten/goldwarden/agent/systemauth/pinentry/keybase-pinentry"
|
||||
)
|
||||
|
||||
func getPassword(title string, description string) (string, error) {
|
||||
pinentryInstance := pinentry.New("", logger.New(""), "")
|
||||
result, err := pinentryInstance.Get(keybase1.SecretEntryArg{
|
||||
Prompt: title,
|
||||
Desc: description,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result.Canceled {
|
||||
return "", errors.New("Cancelled")
|
||||
}
|
||||
|
||||
return result.Text, nil
|
||||
}
|
||||
|
||||
func getApproval(title string, description string) (bool, error) {
|
||||
pinentryInstance := pinentry.New("", logger.New(""), "")
|
||||
result, err := pinentryInstance.Get(keybase1.SecretEntryArg{
|
||||
Prompt: title,
|
||||
Desc: description,
|
||||
Cancel: "Decline",
|
||||
Ok: "Approve",
|
||||
ShowTyping: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if result.Canceled {
|
||||
return false, errors.New("Cancelled")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
27
agent/systemauth/pinentry/keybase-pinentry/LICENSE.md
Normal file
27
agent/systemauth/pinentry/keybase-pinentry/LICENSE.md
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2015, Keybase
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of keybase nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
219
agent/systemauth/pinentry/keybase-pinentry/pinentry.go
Normal file
219
agent/systemauth/pinentry/keybase-pinentry/pinentry.go
Normal file
@ -0,0 +1,219 @@
|
||||
// Copyright 2015 Keybase, Inc. All rights reserved. Use of
|
||||
// this source code is governed by the included BSD license.
|
||||
|
||||
package pinentry
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/keybase/client/go/logger"
|
||||
keybase1 "github.com/keybase/client/go/protocol/keybase1"
|
||||
)
|
||||
|
||||
//
|
||||
// some borrowed from here:
|
||||
//
|
||||
// https://github.com/bradfitz/camlistore/blob/master/pkg/misc/pinentry/pinentry.go
|
||||
//
|
||||
// Under the Apache 2.0 license
|
||||
//
|
||||
|
||||
type Pinentry struct {
|
||||
initRes *error
|
||||
path string
|
||||
term string
|
||||
tty string
|
||||
prog string
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
func New(envprog string, log logger.Logger, tty string) *Pinentry {
|
||||
return &Pinentry{
|
||||
prog: envprog,
|
||||
log: log,
|
||||
tty: tty,
|
||||
}
|
||||
}
|
||||
|
||||
func (pe *Pinentry) Init() (error, error) {
|
||||
if pe.initRes != nil {
|
||||
return *pe.initRes, nil
|
||||
}
|
||||
err, fatalerr := pe.FindProgram()
|
||||
if err == nil {
|
||||
pe.GetTerminalName()
|
||||
}
|
||||
pe.term = os.Getenv("TERM")
|
||||
pe.initRes = &err
|
||||
return err, fatalerr
|
||||
}
|
||||
|
||||
func (pe *Pinentry) SetInitError(e error) {
|
||||
pe.initRes = &e
|
||||
}
|
||||
|
||||
func (pe *Pinentry) FindProgram() (error, error) {
|
||||
prog := pe.prog
|
||||
var err, fatalerr error
|
||||
if len(prog) > 0 {
|
||||
if err = canExec(prog); err == nil {
|
||||
pe.path = prog
|
||||
} else {
|
||||
err = fmt.Errorf("Can't execute given pinentry program '%s': %s",
|
||||
prog, err)
|
||||
fatalerr = err
|
||||
}
|
||||
} else if prog, err = FindPinentry(pe.log); err == nil {
|
||||
pe.path = prog
|
||||
}
|
||||
return err, fatalerr
|
||||
}
|
||||
|
||||
func (pe *Pinentry) Get(arg keybase1.SecretEntryArg) (res *keybase1.SecretEntryRes, err error) {
|
||||
|
||||
pe.log.Debug("+ Pinentry::Get()")
|
||||
|
||||
// Do a lazy initialization
|
||||
if err, _ = pe.Init(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
inst := pinentryInstance{parent: pe}
|
||||
defer inst.Close()
|
||||
|
||||
if err = inst.Init(); err != nil {
|
||||
// We probably shouldn't try to use this thing again if we failed
|
||||
// to set it up.
|
||||
pe.SetInitError(err)
|
||||
return
|
||||
}
|
||||
res, err = inst.Run(arg)
|
||||
pe.log.Debug("- Pinentry::Get() -> %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
func (pi *pinentryInstance) Close() {
|
||||
pi.stdin.Close()
|
||||
pi.cmd.Wait()
|
||||
}
|
||||
|
||||
type pinentryInstance struct {
|
||||
parent *Pinentry
|
||||
cmd *exec.Cmd
|
||||
stdout io.ReadCloser
|
||||
stdin io.WriteCloser
|
||||
br *bufio.Reader
|
||||
}
|
||||
|
||||
func (pi *pinentryInstance) Set(cmd, val string, errp *error) {
|
||||
if val == "" {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(pi.stdin, "%s %s\n", cmd, val)
|
||||
line, _, err := pi.br.ReadLine()
|
||||
if err != nil {
|
||||
*errp = err
|
||||
return
|
||||
}
|
||||
if string(line) != "OK" {
|
||||
*errp = fmt.Errorf("Response to " + cmd + " was " + string(line))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pi *pinentryInstance) Init() (err error) {
|
||||
parent := pi.parent
|
||||
|
||||
parent.log.Debug("+ pinentryInstance::Init()")
|
||||
|
||||
pi.cmd = exec.Command(parent.path)
|
||||
pi.stdin, _ = pi.cmd.StdinPipe()
|
||||
pi.stdout, _ = pi.cmd.StdoutPipe()
|
||||
|
||||
if err = pi.cmd.Start(); err != nil {
|
||||
parent.log.Warning("unexpected error running pinentry (%s): %s", parent.path, err)
|
||||
return
|
||||
}
|
||||
|
||||
pi.br = bufio.NewReader(pi.stdout)
|
||||
lineb, _, err := pi.br.ReadLine()
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to get getpin greeting: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
line := string(lineb)
|
||||
if !strings.HasPrefix(line, "OK") {
|
||||
err = fmt.Errorf("getpin greeting didn't say 'OK', said: %q", line)
|
||||
return
|
||||
}
|
||||
|
||||
if len(parent.tty) > 0 {
|
||||
parent.log.Debug("setting ttyname to %s", parent.tty)
|
||||
pi.Set("OPTION", "ttyname="+parent.tty, &err)
|
||||
if err != nil {
|
||||
parent.log.Debug("error setting ttyname: %s", err)
|
||||
}
|
||||
}
|
||||
if len(parent.term) > 0 {
|
||||
parent.log.Debug("setting ttytype to %s", parent.term)
|
||||
pi.Set("OPTION", "ttytype="+parent.term, &err)
|
||||
if err != nil {
|
||||
parent.log.Debug("error setting ttytype: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
parent.log.Debug("- pinentryInstance::Init() -> %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
func descEncode(s string) string {
|
||||
s = strings.Replace(s, "%", "%%", -1)
|
||||
s = strings.Replace(s, "\n", "%0A", -1)
|
||||
return s
|
||||
}
|
||||
|
||||
func resDecode(s string) string {
|
||||
s = strings.Replace(s, "%25", "%", -1)
|
||||
return s
|
||||
}
|
||||
|
||||
func (pi *pinentryInstance) Run(arg keybase1.SecretEntryArg) (res *keybase1.SecretEntryRes, err error) {
|
||||
|
||||
pi.Set("SETPROMPT", arg.Prompt, &err)
|
||||
pi.Set("SETDESC", descEncode(arg.Desc), &err)
|
||||
pi.Set("SETOK", arg.Ok, &err)
|
||||
pi.Set("SETCANCEL", arg.Cancel, &err)
|
||||
pi.Set("SETERROR", arg.Err, &err)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(pi.stdin, "GETPIN\n")
|
||||
var lineb []byte
|
||||
lineb, _, err = pi.br.ReadLine()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to read line after GETPIN: %v", err)
|
||||
return
|
||||
}
|
||||
line := string(lineb)
|
||||
switch {
|
||||
case strings.HasPrefix(line, "D "):
|
||||
res = &keybase1.SecretEntryRes{Text: resDecode(line[2:])}
|
||||
case strings.HasPrefix(line, "ERR 83886179 canceled") || strings.HasPrefix(line, "ERR 83886179 Operation cancelled"):
|
||||
res = &keybase1.SecretEntryRes{Canceled: true}
|
||||
case line == "OK":
|
||||
res = &keybase1.SecretEntryRes{}
|
||||
default:
|
||||
return nil, fmt.Errorf("GETPIN response didn't start with D; got %q", line)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
111
agent/systemauth/pinentry/keybase-pinentry/pinentry_nix.go
Normal file
111
agent/systemauth/pinentry/keybase-pinentry/pinentry_nix.go
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright 2015 Keybase, Inc. All rights reserved. Use of
|
||||
// this source code is governed by the included BSD license.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris
|
||||
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
|
||||
|
||||
package pinentry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/keybase/client/go/logger"
|
||||
)
|
||||
|
||||
//
|
||||
// some borrowed from here:
|
||||
//
|
||||
// https://github.com/bradfitz/camlistore/blob/master/pkg/misc/pinentry/pinentry.go
|
||||
//
|
||||
// Under the Apache 2.0 license
|
||||
//
|
||||
|
||||
func canExec(s string) error {
|
||||
fi, err := os.Stat(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mode := fi.Mode()
|
||||
|
||||
//
|
||||
// Only consider non-directories that have at least one +x
|
||||
// bit set.
|
||||
//
|
||||
// TODO: Recheck this on windows!
|
||||
// See here for lookpath: http://golang.org/src/pkg/os/exec/lp_windows.go
|
||||
//
|
||||
// Similar to check from exec.LookPath below
|
||||
// See here: http://golang.org/src/pkg/os/exec/lp_unix.go
|
||||
//
|
||||
if mode.IsDir() {
|
||||
return fmt.Errorf("Program '%s' is a directory", s)
|
||||
} else if int(mode)&0111 == 0 {
|
||||
return fmt.Errorf("Program '%s' isn't executable", s)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func FindPinentry(log logger.Logger) (string, error) {
|
||||
if !HasWindows() {
|
||||
return "", fmt.Errorf("Can't spawn gui window, not using pinentry")
|
||||
}
|
||||
bins := []string{
|
||||
// If you install MacTools you'll wind up with this pinentry
|
||||
"/usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac",
|
||||
}
|
||||
|
||||
extraPaths := []string{}
|
||||
|
||||
log.Debug("+ FindPinentry()")
|
||||
|
||||
cmds := []string{
|
||||
"pinentry-gtk-2",
|
||||
"pinentry-qt4",
|
||||
"pinentry",
|
||||
}
|
||||
|
||||
checkFull := func(s string) bool {
|
||||
log.Debug("| Check fullpath %s", s)
|
||||
found := (canExec(s) == nil)
|
||||
if found {
|
||||
log.Debug("- Found: %s", s)
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
for _, b := range bins {
|
||||
if checkFull(b) {
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
|
||||
path := os.Getenv("PATH")
|
||||
for _, c := range cmds {
|
||||
log.Debug("| Looking for %s in standard PATH %s", c, path)
|
||||
fullc, err := exec.LookPath(c)
|
||||
if err == nil {
|
||||
log.Debug("- Found %s", fullc)
|
||||
return fullc, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, ep := range extraPaths {
|
||||
for _, c := range cmds {
|
||||
full := filepath.Join(ep, c)
|
||||
if checkFull(full) {
|
||||
return full, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("- FindPinentry: none found")
|
||||
return "", fmt.Errorf("No pinentry found, checked a bunch of different places")
|
||||
}
|
||||
|
||||
func (pe *Pinentry) GetTerminalName() {
|
||||
// Noop on all platforms but windows
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// Copyright 2015 Keybase, Inc. All rights reserved. Use of
|
||||
// this source code is governed by the included BSD license.
|
||||
|
||||
//go:build !darwin
|
||||
// +build !darwin
|
||||
|
||||
package pinentry
|
||||
|
||||
type pinentrySecretStoreInfo struct{}
|
||||
|
||||
func (pi *pinentryInstance) useSecretStore(useSecretStore bool) (pinentrySecretStoreInfo, error) {
|
||||
return pinentrySecretStoreInfo{}, nil
|
||||
}
|
||||
|
||||
func (pi *pinentryInstance) shouldStoreSecret(info pinentrySecretStoreInfo) bool {
|
||||
return false
|
||||
}
|
28
agent/systemauth/pinentry/keybase-pinentry/pinentry_osx.go
Normal file
28
agent/systemauth/pinentry/keybase-pinentry/pinentry_osx.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2015 Keybase, Inc. All rights reserved. Use of
|
||||
// this source code is governed by the included BSD license.
|
||||
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package pinentry
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type pinentrySecretStoreInfo string
|
||||
|
||||
func (pi *pinentryInstance) useSecretStore(useSecretStore bool) (pinentrySecretStoreInfo, error) {
|
||||
// unimplemented
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (pi *pinentryInstance) shouldStoreSecret(info pinentrySecretStoreInfo) bool {
|
||||
// unimplemted
|
||||
return false
|
||||
}
|
||||
|
||||
func HasWindows() bool {
|
||||
// We aren't in an ssh connection, so we can probably spawn a window.
|
||||
return len(os.Getenv("SSH_CONNECTION")) == 0
|
||||
}
|
112
agent/systemauth/pinentry/keybase-pinentry/pinentry_windows.go
Normal file
112
agent/systemauth/pinentry/keybase-pinentry/pinentry_windows.go
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright 2015 Keybase, Inc. All rights reserved. Use of
|
||||
// this source code is governed by the included BSD license.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package pinentry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/keybase/client/go/logger"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
func HasWindows() bool {
|
||||
// We're assuming you aren't using windows remotely.
|
||||
return true
|
||||
}
|
||||
|
||||
// LookPath searches for an executable binary named file
|
||||
// in the directories named by the PATH environment variable.
|
||||
// If file contains a slash, it is tried directly and the PATH is not consulted.
|
||||
|
||||
func canExec(s string) error {
|
||||
if strings.IndexAny(s, `:\/`) == -1 {
|
||||
s += string(filepath.Separator)
|
||||
}
|
||||
_, err := exec.LookPath(s)
|
||||
return err
|
||||
}
|
||||
|
||||
func FindPinentry(log logger.Logger) (string, error) {
|
||||
|
||||
// // If you install GPG you'll wind up with this pinentry
|
||||
// C:\Program Files (x86)\GNU\GnuPG\pinentry-gtk-2.exe
|
||||
// C:\Program Files (x86)\GNU\GnuPG\pinentry-qt4.exe
|
||||
// C:\Program Files (x86)\GNU\GnuPG\pinentry-w32.exe
|
||||
// C:\Program Files (x86)\GNU\GnuPG\pinentry.exe
|
||||
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Wow6432Node\GNU\GnuPG`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
k, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\GNU\GnuPG`, registry.QUERY_VALUE)
|
||||
}
|
||||
if err != nil {
|
||||
log.Debug("- FindPinentry: can't open registry")
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
installDir, _, err := k.GetStringValue("Install Directory")
|
||||
if err != nil {
|
||||
log.Debug("- FindPinentry: can't get string from registry")
|
||||
}
|
||||
|
||||
extraPaths := []string{}
|
||||
|
||||
log.Debug("+ FindPinentry()")
|
||||
|
||||
cmds := []string{
|
||||
"pinentry-gtk-2.exe",
|
||||
"pinentry-qt4.exe",
|
||||
"pinentry-w32.exe",
|
||||
"pinentry.exe",
|
||||
}
|
||||
|
||||
// First, look where the registry points
|
||||
for _, c := range cmds {
|
||||
full := filepath.Join(installDir, c)
|
||||
log.Debug("| (registry) Looking for %s", full)
|
||||
_, err := exec.LookPath(full)
|
||||
if err == nil {
|
||||
return full, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Look in program files, just in case
|
||||
extraPaths = append(extraPaths, os.Getenv("ProgramFiles"))
|
||||
extraPaths = append(extraPaths, os.Getenv("ProgramFiles(x86)"))
|
||||
|
||||
for _, ep := range extraPaths {
|
||||
for _, c := range cmds {
|
||||
full := filepath.Join(ep, "GNU", "GnuPG", c)
|
||||
log.Debug("| Looking for %s", full)
|
||||
_, err := exec.LookPath(full)
|
||||
if err == nil {
|
||||
return full, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ep := range extraPaths {
|
||||
for _, c := range cmds {
|
||||
full := filepath.Join(ep, "Gpg4win", "bin", c)
|
||||
log.Debug("| Looking for %s", full)
|
||||
_, err := exec.LookPath(full)
|
||||
if err == nil {
|
||||
return full, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("- FindPinentry: none found")
|
||||
return "", fmt.Errorf("No pinentry found, checked a bunch of different places")
|
||||
}
|
||||
|
||||
func (pe *Pinentry) GetTerminalName() {
|
||||
pe.tty = "windows"
|
||||
}
|
14
agent/systemauth/pinentry/keybase-pinentry/pinentry_x11.go
Normal file
14
agent/systemauth/pinentry/keybase-pinentry/pinentry_x11.go
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2015 Keybase, Inc. All rights reserved. Use of
|
||||
// this source code is governed by the included BSD license.
|
||||
|
||||
//go:build dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris
|
||||
// +build dragonfly freebsd linux nacl netbsd openbsd solaris
|
||||
|
||||
package pinentry
|
||||
|
||||
import "os"
|
||||
|
||||
func HasWindows() bool {
|
||||
//If there is a DISPLAY then we can spawn a window to it.
|
||||
return len(os.Getenv("DISPLAY")) > 0
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
//go:build !linux
|
||||
//go:build !linux && !windows && !darwin && !freebsd
|
||||
|
||||
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")
|
||||
}
|
||||
|
@ -14,14 +14,15 @@ import (
|
||||
|
||||
var log = logging.GetLogger("Goldwarden", "Systemauth")
|
||||
|
||||
const tokenExpiry = 10 * time.Minute
|
||||
const tokenExpiry = 60 * time.Minute
|
||||
const SSHTTL = 60 * time.Minute
|
||||
|
||||
type SessionType string
|
||||
|
||||
const (
|
||||
AccessVault SessionType = "com.quexten.goldwarden.accessvault"
|
||||
SSHKey SessionType = "com.quexten.goldwarden.usesshkey"
|
||||
Pin SessionType = "com.quexten.goldwarden.pin" // this counts as all other permissions
|
||||
Pin SessionType = "com.quexten.goldwarden.pin"
|
||||
)
|
||||
|
||||
var sessionStore = SessionStore{
|
||||
@ -40,12 +41,12 @@ type SessionStore struct {
|
||||
Store []Session
|
||||
}
|
||||
|
||||
func (s *SessionStore) CreateSession(pid int, parentpid int, grandparentpid int, sessionType SessionType) Session {
|
||||
func (s *SessionStore) CreateSession(pid int, parentpid int, grandparentpid int, sessionType SessionType, ttl time.Duration) Session {
|
||||
var session = Session{
|
||||
Pid: pid,
|
||||
ParentPid: parentpid,
|
||||
GrandParentPid: grandparentpid,
|
||||
Expires: time.Now().Add(tokenExpiry),
|
||||
Expires: time.Now().Add(ttl),
|
||||
sessionType: sessionType,
|
||||
}
|
||||
s.Store = append(s.Store, session)
|
||||
@ -54,7 +55,7 @@ func (s *SessionStore) CreateSession(pid int, parentpid int, grandparentpid int,
|
||||
|
||||
func (s *SessionStore) verifySession(ctx sockets.CallingContext, sessionType SessionType) bool {
|
||||
for _, session := range s.Store {
|
||||
if session.ParentPid == ctx.ParentProcessPid && session.GrandParentPid == ctx.GrandParentProcessPid && (session.sessionType == sessionType || session.sessionType == Pin) {
|
||||
if session.ParentPid == ctx.ParentProcessPid && session.GrandParentPid == ctx.GrandParentProcessPid && session.sessionType == sessionType {
|
||||
if session.Expires.After(time.Now()) {
|
||||
return true
|
||||
}
|
||||
@ -65,6 +66,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
|
||||
@ -81,19 +86,21 @@ func GetPermission(sessionType SessionType, ctx sockets.CallingContext, config *
|
||||
if sessionStore.verifySession(ctx, sessionType) {
|
||||
log.Info("Permission granted from cached session")
|
||||
} else {
|
||||
if biometrics.BiometricsWorking() {
|
||||
biometricsApproval := biometrics.CheckBiometrics(biometricsApprovalType)
|
||||
if !biometricsApproval {
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
log.Warn("Biometrics is not available, asking for pin")
|
||||
pin, err := pinentry.GetPassword("Enter PIN", "Biometrics is not available. Enter your pin to authorize this action. "+message)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !config.VerifyPin(pin) {
|
||||
return false, nil
|
||||
if !sessionStore.verifySession(ctx, Pin) {
|
||||
if biometrics.BiometricsWorking() {
|
||||
biometricsApproval := biometrics.CheckBiometrics(biometricsApprovalType)
|
||||
if !biometricsApproval {
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
log.Warn("Biometrics is not available, asking for pin")
|
||||
pin, err := pinentry.GetPassword("Enter PIN", "Biometrics is not available. Enter your pin to authorize this action. "+message)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !config.VerifyPin(pin) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +110,7 @@ func GetPermission(sessionType SessionType, ctx sockets.CallingContext, config *
|
||||
// }
|
||||
|
||||
log.Info("Permission granted, creating session")
|
||||
sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid, sessionType)
|
||||
sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid, sessionType, tokenExpiry)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@ -124,10 +131,22 @@ func CheckBiometrics(callingContext *sockets.CallingContext, approvalType biomet
|
||||
return approval
|
||||
}
|
||||
|
||||
func CreatePinSession(ctx sockets.CallingContext) {
|
||||
sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid, Pin)
|
||||
func CreatePinSession(ctx sockets.CallingContext, ttl time.Duration) Session {
|
||||
return sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid, Pin, ttl)
|
||||
}
|
||||
|
||||
func VerifyPinSession(ctx sockets.CallingContext) bool {
|
||||
return sessionStore.verifySession(ctx, Pin)
|
||||
}
|
||||
|
||||
func CreateSSHSession(ctx sockets.CallingContext) Session {
|
||||
return sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid, SSHKey, SSHTTL)
|
||||
}
|
||||
|
||||
func GetSSHSession(ctx sockets.CallingContext) bool {
|
||||
return sessionStore.verifySession(ctx, SSHKey)
|
||||
}
|
||||
|
||||
func WipeSessions() {
|
||||
sessionStore.Store = []Session{}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -16,6 +17,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 +47,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 +64,213 @@ 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{}) {
|
||||
if cfg.ConfigFile.RuntimeConfig.DaemonAuthToken == "" {
|
||||
return
|
||||
}
|
||||
|
||||
req := messages.ParsePayload(msg).(messages.SessionAuthRequest)
|
||||
verified := subtle.ConstantTimeCompare([]byte(cfg.ConfigFile.RuntimeConfig.DaemonAuthToken), []byte(req.Token)) == 1
|
||||
|
||||
payload := messages.SessionAuthResponse{
|
||||
Verified: verified,
|
||||
}
|
||||
log.Info("Verified: %t", verified)
|
||||
callingContext := sockets.GetCallingContext(c)
|
||||
if verified {
|
||||
systemauth.CreatePinSession(callingContext, 365*24*time.Hour) // permanent session
|
||||
}
|
||||
|
||||
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{}) {
|
||||
// todo lockdown this method better
|
||||
if cfg.ConfigFile.RuntimeConfig.DaemonAuthToken == "" {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
@ -116,15 +325,6 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
|
||||
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
|
||||
}
|
||||
@ -175,6 +375,7 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
|
||||
cfg.Lock()
|
||||
vault.Clear()
|
||||
vault.Keyring.Lock()
|
||||
systemauth.WipeSessions()
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("Could not monitor screensaver: %s", err.Error())
|
||||
@ -182,7 +383,10 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
|
||||
}()
|
||||
go func() {
|
||||
err = processsecurity.MonitorIdle(func() {
|
||||
log.Warn("Idling detected but no action is implemented")
|
||||
cfg.Lock()
|
||||
vault.Clear()
|
||||
vault.Keyring.Lock()
|
||||
systemauth.WipeSessions()
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("Could not monitor idle: %s", err.Error())
|
||||
@ -291,19 +495,20 @@ 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()
|
||||
log.Info("Agent listening on %s...", path)
|
||||
|
||||
go func() {
|
||||
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
|
||||
}
|
@ -49,7 +49,9 @@ func TypeString(textToType string) {
|
||||
result := message.Body[1].(map[string]dbus.Variant)
|
||||
resultSessionHandle := result["session_handle"]
|
||||
sessionHandle = dbus.ObjectPath(resultSessionHandle.String()[1 : len(resultSessionHandle.String())-1])
|
||||
res := obj.Call("org.freedesktop.portal.RemoteDesktop.SelectDevices", 0, sessionHandle, map[string]dbus.Variant{})
|
||||
res := obj.Call("org.freedesktop.portal.RemoteDesktop.SelectDevices", 0, sessionHandle, map[string]dbus.Variant{
|
||||
"types": dbus.MakeVariant(uint32(1)),
|
||||
})
|
||||
if res.Err != nil {
|
||||
log.Error("Error selecting devices: %s", res.Err.Error())
|
||||
}
|
||||
@ -64,7 +66,7 @@ func TypeString(textToType string) {
|
||||
} else if state == 2 {
|
||||
log.Info("Performing Typing")
|
||||
state = 3
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
for _, char := range textToType {
|
||||
if char == '\t' {
|
||||
obj.Call("org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeycode", 0, sessionHandle, map[string]dbus.Variant{}, 15, uint32(1))
|
||||
|
@ -108,19 +108,40 @@ func detectAndInstallBrowsers(startPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
binPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() && info.Name() == "native-messaging-hosts" {
|
||||
fmt.Printf("Found mozilla-like browser: %s\n", path)
|
||||
manifest := strings.Replace(templateMozilla, "PATH", binPath, 1)
|
||||
err = os.WriteFile(path+"/com.8bit.bitwarden.json", []byte(manifest), 0644)
|
||||
|
||||
os.Chown(path+"/com.8bit.bitwarden.json", 7, 7)
|
||||
os.Remove(path + "/com.8bit.bitwarden.json")
|
||||
os.Chown(path+"/goldwarden-proxy.sh", 7, 7)
|
||||
os.Remove(path + "/goldwarden-proxy.sh")
|
||||
|
||||
manifest := strings.Replace(templateMozilla, "PATH", path+"/goldwarden-proxy.sh", 1)
|
||||
err = os.WriteFile(path+"/com.8bit.bitwarden.json", []byte(manifest), 0444)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(path+"/goldwarden-proxy.sh", []byte(proxyScript), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if info.IsDir() && info.Name() == "NativeMessagingHosts" {
|
||||
fmt.Printf("Found chrome-like browser: %s\n", path)
|
||||
manifest := strings.Replace(templateChrome, "PATH", binPath, 1)
|
||||
err = os.WriteFile(path+"/com.8bit.bitwarden.json", []byte(manifest), 0644)
|
||||
|
||||
os.Chown(path+"/com.8bit.bitwarden.json", 7, 7)
|
||||
os.Remove(path + "/com.8bit.bitwarden.json")
|
||||
os.Chown(path+"/goldwarden-proxy.sh", 7, 7)
|
||||
os.Remove(path + "/goldwarden-proxy.sh")
|
||||
|
||||
manifest := strings.Replace(templateChrome, "PATH", path+"/goldwarden-proxy.sh", 1)
|
||||
err = os.WriteFile(path+"/com.8bit.bitwarden.json", []byte(manifest), 0444)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(path+"/goldwarden-proxy.sh", []byte(proxyScript), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -21,3 +21,14 @@ const templateChrome = `{
|
||||
"chrome-extension://ccnckbpmaceehanjmeomladnmlffdjgn/"
|
||||
]
|
||||
}`
|
||||
|
||||
const proxyScript = `#!/bin/bash
|
||||
|
||||
# Check if the "com.quexten.Goldwarden" Flatpak is installed
|
||||
if flatpak list | grep -q "com.quexten.Goldwarden"; then
|
||||
flatpak run --command=goldwarden com.quexten.Goldwarden "$@"
|
||||
else
|
||||
# If not installed, attempt to run the local version
|
||||
goldwarden "$@"
|
||||
fi
|
||||
`
|
||||
|
@ -1,5 +0,0 @@
|
||||
package client
|
||||
|
||||
type Client interface {
|
||||
SendToAgent(request interface{}) (interface{}, error)
|
||||
}
|
@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func VerifySetup(runtimeConfig config.RuntimeConfig) bool {
|
||||
if !cmd.IsPolkitSetup() && !runtimeConfig.DisableAuth {
|
||||
if !cmd.IsPolkitSetup() {
|
||||
fmt.Println("Polkit is not setup. Run 'goldwarden setup polkit' to set it up.")
|
||||
return false
|
||||
}
|
||||
|
@ -3,26 +3,29 @@ package client
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/quexten/goldwarden/agent/config"
|
||||
"github.com/quexten/goldwarden/ipc/messages"
|
||||
)
|
||||
|
||||
const READ_BUFFER = 1 * 1024 * 1024 // 1MB
|
||||
const READ_BUFFER = 4 * 1024 * 1024 // 16MB
|
||||
|
||||
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
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
|
||||
"github.com/quexten/goldwarden/autotype"
|
||||
@ -10,18 +12,18 @@ 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
|
||||
password := os.Getenv("PASSWORD")
|
||||
autotype.TypeString(username + "\t" + password)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
textHex, _ := reader.ReadString('\n')
|
||||
text, _ := hex.DecodeString(textHex)
|
||||
autotype.TypeString(string(text))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(autofillCmd)
|
||||
autofillCmd.PersistentFlags().String("username", "", "")
|
||||
}
|
||||
|
141
cmd/config.go
141
cmd/config.go
@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@ -30,12 +31,12 @@ var setApiUrlCmd = &cobra.Command{
|
||||
switch result.(type) {
|
||||
case messages.ActionResponse:
|
||||
if result.(messages.ActionResponse).Success {
|
||||
println("Done")
|
||||
fmt.Println("Done")
|
||||
} else {
|
||||
println("Setting api url failed: " + result.(messages.ActionResponse).Message)
|
||||
fmt.Println("Setting api url failed: " + result.(messages.ActionResponse).Message)
|
||||
}
|
||||
default:
|
||||
println("Wrong IPC response type")
|
||||
fmt.Println("Wrong IPC response type")
|
||||
}
|
||||
|
||||
},
|
||||
@ -63,12 +64,12 @@ var setIdentityURLCmd = &cobra.Command{
|
||||
switch result.(type) {
|
||||
case messages.ActionResponse:
|
||||
if result.(messages.ActionResponse).Success {
|
||||
println("Done")
|
||||
fmt.Println("Done")
|
||||
} else {
|
||||
println("Setting identity url failed: " + result.(messages.ActionResponse).Message)
|
||||
fmt.Println("Setting identity url failed: " + result.(messages.ActionResponse).Message)
|
||||
}
|
||||
default:
|
||||
println("Wrong IPC response type")
|
||||
fmt.Println("Wrong IPC response type")
|
||||
}
|
||||
|
||||
},
|
||||
@ -96,17 +97,111 @@ 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")
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
var setVaultURLCmd = &cobra.Command{
|
||||
Use: "set-vault-url",
|
||||
Short: "Set the vault url",
|
||||
Long: `Set the vault url.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
url := args[0]
|
||||
request := messages.SetVaultURLRequest{}
|
||||
request.Value = url
|
||||
|
||||
result, err := commandClient.SendToAgent(request)
|
||||
if err != nil {
|
||||
handleSendToAgentError(err)
|
||||
return
|
||||
}
|
||||
|
||||
switch result.(type) {
|
||||
case messages.ActionResponse:
|
||||
if result.(messages.ActionResponse).Success {
|
||||
fmt.Println("Done")
|
||||
} else {
|
||||
fmt.Println("Setting vault url failed: " + result.(messages.ActionResponse).Message)
|
||||
}
|
||||
default:
|
||||
fmt.Println("Wrong IPC response type")
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
var setURLsAutomaticallyCmd = &cobra.Command{
|
||||
Use: "set-server",
|
||||
Short: "Set the urls automatically",
|
||||
Long: `Set the api/identity/vault/notification urls automaticall from a base url.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
value := args[0]
|
||||
request := messages.SetURLsAutomaticallyRequest{}
|
||||
request.Value = value
|
||||
|
||||
result, err := commandClient.SendToAgent(request)
|
||||
if err != nil {
|
||||
handleSendToAgentError(err)
|
||||
return
|
||||
}
|
||||
|
||||
switch result.(type) {
|
||||
case messages.ActionResponse:
|
||||
if result.(messages.ActionResponse).Success {
|
||||
fmt.Println("Done")
|
||||
} else {
|
||||
fmt.Println("Setting urls automatically failed: " + result.(messages.ActionResponse).Message)
|
||||
}
|
||||
default:
|
||||
fmt.Println("Wrong IPC response type")
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
var getEnvironmentCmd = &cobra.Command{
|
||||
Use: "get-environment",
|
||||
Short: "Get the environment",
|
||||
Long: `Get the environment.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
request := messages.GetConfigEnvironmentRequest{}
|
||||
|
||||
result, err := commandClient.SendToAgent(request)
|
||||
if err != nil {
|
||||
handleSendToAgentError(err)
|
||||
return
|
||||
}
|
||||
|
||||
switch result := result.(type) {
|
||||
case messages.GetConfigEnvironmentResponse:
|
||||
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:
|
||||
fmt.Println("Wrong IPC response type")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var setApiClientIDCmd = &cobra.Command{
|
||||
Use: "set-client-id",
|
||||
Short: "Set the client id",
|
||||
@ -133,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")
|
||||
}
|
||||
|
||||
},
|
||||
@ -170,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")
|
||||
}
|
||||
|
||||
},
|
||||
@ -196,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")
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -218,6 +314,9 @@ func init() {
|
||||
configCmd.AddCommand(setApiUrlCmd)
|
||||
configCmd.AddCommand(setIdentityURLCmd)
|
||||
configCmd.AddCommand(setNotificationsURLCmd)
|
||||
configCmd.AddCommand(setVaultURLCmd)
|
||||
configCmd.AddCommand(setURLsAutomaticallyCmd)
|
||||
configCmd.AddCommand(getEnvironmentCmd)
|
||||
configCmd.AddCommand(getRuntimeConfigCmd)
|
||||
configCmd.AddCommand(setApiClientIDCmd)
|
||||
configCmd.AddCommand(setApiSecretCmd)
|
||||
|
@ -1,8 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/quexten/goldwarden/agent"
|
||||
@ -19,23 +19,34 @@ 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")
|
||||
}
|
||||
|
||||
go func() {
|
||||
signalChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChannel, os.Interrupt)
|
||||
<-signalChannel
|
||||
cleanup := func() {
|
||||
fmt.Println("removing sockets and exiting")
|
||||
fmt.Println("unlinking", runtimeConfig.GoldwardenSocketPath)
|
||||
err := syscall.Unlink(runtimeConfig.GoldwardenSocketPath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println("unlinking", runtimeConfig.SSHAgentSocketPath)
|
||||
err = syscall.Unlink(runtimeConfig.SSHAgentSocketPath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println("memguard wiping memory and exiting")
|
||||
memguard.SafeExit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
err := agent.StartUnixAgent(runtimeConfig.GoldwardenSocketPath, runtimeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cleanup()
|
||||
},
|
||||
}
|
||||
|
||||
|
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,31 +72,24 @@ 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),
|
||||
"username": stringsx.Clean(login.Username),
|
||||
"password": stringsx.Clean(strings.ReplaceAll(login.Password, "\"", "\\\"")),
|
||||
"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
|
||||
}
|
||||
|
||||
|
53
cmd/send.go
Normal file
53
cmd/send.go
Normal file
@ -0,0 +1,53 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/quexten/goldwarden/ipc/messages"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var sendCmd = &cobra.Command{
|
||||
Use: "send",
|
||||
Short: "Commands for managing sends",
|
||||
Long: `Commands for managing sends.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
var sendCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Uploads a Bitwarden send.",
|
||||
Long: `Uploads a Bitwarden send.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
loginIfRequired()
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
text, _ := cmd.Flags().GetString("text")
|
||||
|
||||
result, err := commandClient.SendToAgent(messages.CreateSendRequest{
|
||||
Name: name,
|
||||
Text: text,
|
||||
})
|
||||
if err != nil {
|
||||
handleSendToAgentError(err)
|
||||
return
|
||||
}
|
||||
|
||||
switch result.(type) {
|
||||
case messages.CreateSendResponse:
|
||||
fmt.Println("Send created: " + result.(messages.CreateSendResponse).URL)
|
||||
break
|
||||
case messages.ActionResponse:
|
||||
fmt.Println("Error: " + result.(messages.ActionResponse).Message)
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(sendCmd)
|
||||
sendCmd.AddCommand(sendCreateCmd)
|
||||
sendCreateCmd.Flags().StringP("name", "n", "", "Name of the send")
|
||||
sendCreateCmd.Flags().StringP("text", "t", "", "Text of the send")
|
||||
}
|
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)
|
||||
}
|
@ -4,8 +4,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strings"
|
||||
|
||||
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
|
||||
@ -13,7 +15,20 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func isRoot() bool {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatalf("[isRoot] Unable to get current user: %s", err)
|
||||
}
|
||||
return currentUser.Username == "root"
|
||||
}
|
||||
|
||||
func setupPolkit() {
|
||||
if isRoot() {
|
||||
fmt.Println("Do not run this command as root!")
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Create("/tmp/goldwarden-policy")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -77,6 +92,11 @@ ExecStart=BINARY_PATH daemonize
|
||||
WantedBy=graphical-session.target`
|
||||
|
||||
func setupSystemd() {
|
||||
if isRoot() {
|
||||
fmt.Println("Do not run this command as root!")
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Create("/tmp/goldwarden.service")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -125,6 +145,11 @@ var systemdCmd = &cobra.Command{
|
||||
Short: "Sets up systemd autostart",
|
||||
Long: "Sets up systemd autostart",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if isRoot() {
|
||||
fmt.Println("Do not run this command as root!")
|
||||
return
|
||||
}
|
||||
|
||||
setupSystemd()
|
||||
},
|
||||
}
|
||||
@ -134,6 +159,11 @@ var browserbiometricsCmd = &cobra.Command{
|
||||
Short: "Sets up browser biometrics",
|
||||
Long: "Sets up browser biometrics",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if isRoot() {
|
||||
fmt.Println("Do not run this command as root!")
|
||||
return
|
||||
}
|
||||
|
||||
err := browserbiometrics.DetectAndInstallBrowsers()
|
||||
if err != nil {
|
||||
fmt.Println("Error: " + err.Error())
|
||||
|
@ -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
|
||||
@ -17,34 +17,27 @@ finish-args:
|
||||
- --talk-name=org.gnome.ScreenSaver
|
||||
- --talk-name=org.freedesktop.ScreenSaver
|
||||
|
||||
# Lock on idle
|
||||
- --talk-name=org.gnome.Mutter.IdleMonitor
|
||||
|
||||
# Notifications
|
||||
- --talk-name=org.freedesktop.Notifications
|
||||
|
||||
# Home directory access to setup browser ipc, can posibly restrict this further if requried by listing each browser's nativehost directory separately
|
||||
- --filesystem=home
|
||||
|
||||
# pinentry & approval
|
||||
- --talk-name=org.gnome.keyring.SystemPrompter
|
||||
# biometric / user password auth
|
||||
- --system-talk-name=org.freedesktop.PolicyKit1
|
||||
modules:
|
||||
- ./ui/python3-requirements.json
|
||||
- name: wl-clipboard
|
||||
buildsystem: meson
|
||||
config-opts:
|
||||
- -Dfishcompletiondir=no
|
||||
sources:
|
||||
- type: git
|
||||
url: https://github.com/bugaevc/wl-clipboard.git
|
||||
tag: v2.2.1
|
||||
- ./gui/python3-requirements.json
|
||||
- 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/
|
||||
- mkdir -p /app/bin
|
||||
- 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: ./
|
||||
|
22
go.mod
22
go.mod
@ -4,35 +4,49 @@ go 1.20
|
||||
|
||||
require (
|
||||
github.com/LlamaNite/llamalog v0.2.1
|
||||
github.com/Microsoft/go-winio v0.6.1
|
||||
github.com/amenzhinsky/go-polkit v0.0.0-20210519083301-ee6a51849123
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/awnumar/memguard v0.22.4
|
||||
github.com/gen2brain/beeep v0.0.0-20240112042604-c7bb2cd88fea
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/icza/gox v0.0.0-20230924165045-adcb03233bb5
|
||||
github.com/keybase/client/go v0.0.0-20240202160538-668db6be75e4
|
||||
github.com/lox/go-touchid v0.0.0-20170712105233-619cc8e578d0
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/tink-crypto/tink-go/v2 v2.1.0
|
||||
github.com/twpayne/go-pinentry v0.3.0
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848
|
||||
golang.org/x/sys v0.16.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/keybase/backoff v1.0.1-0.20160517061000-726b63b835ec // indirect
|
||||
github.com/keybase/clockwork v0.1.1-0.20161209210251-976f45f4a979 // indirect
|
||||
github.com/keybase/go-codec v0.0.0-20180928230036-164397562123 // indirect
|
||||
github.com/keybase/go-framed-msgpack-rpc v0.0.0-20230103225103-1f052922b096 // indirect
|
||||
github.com/keybase/go-jsonw v0.0.0-20200325173637-df90f282c233 // indirect
|
||||
github.com/keybase/go-logging v0.0.0-20231213204715-4b3ff33ba5b6 // indirect
|
||||
github.com/keybase/msgpackzip v0.0.0-20221220225959-4abf538d2b9c // indirect
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/tools v0.16.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/awnumar/memcall v0.2.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/keys-pub/go-libfido2 v1.5.3
|
||||
github.com/keys-pub/go-libfido2 v1.5.4-0.20230628153049-536daffdd394
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
|
54
go.sum
54
go.sum
@ -1,5 +1,7 @@
|
||||
github.com/LlamaNite/llamalog v0.2.1 h1:k9XugHmyQqJhCrogca808Jl2rrEKIWMtWyLKX+xX9Mg=
|
||||
github.com/LlamaNite/llamalog v0.2.1/go.mod h1:zopgmWk8utZPfZCPa/uvQkv99Lan3pRrw/9inbIYZeo=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/amenzhinsky/go-polkit v0.0.0-20210519083301-ee6a51849123 h1:VdNhe94PF9yn6KudYnpcBb6bH7l+wsEy9yn6Ulm1/j8=
|
||||
@ -10,12 +12,16 @@ github.com/awnumar/memcall v0.2.0 h1:sRaogqExTOOkkNwO9pzJsL8jrOV29UuUW7teRMfbqtI
|
||||
github.com/awnumar/memcall v0.2.0/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo=
|
||||
github.com/awnumar/memguard v0.22.4 h1:1PLgKcgGPeExPHL8dCOWGVjIbQUBgJv9OL0F/yE1PqQ=
|
||||
github.com/awnumar/memguard v0.22.4/go.mod h1:+APmZGThMBWjnMlKiSM1X7MVpbIVewen2MTkqWkA/zE=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/gen2brain/beeep v0.0.0-20240112042604-c7bb2cd88fea h1:oWUHxzaBvwkRWiINbBOY39XIF+n9b4RJEPHdQ8waJUo=
|
||||
github.com/gen2brain/beeep v0.0.0-20240112042604-c7bb2cd88fea/go.mod h1:0W7dI87PvXJ1Sjs0QPvWXKcQmNERY77e8l7GFhZB/s4=
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@ -29,8 +35,24 @@ github.com/icza/gox v0.0.0-20230924165045-adcb03233bb5 h1:K7KEFpKgVcjj98jOu2Z3xM
|
||||
github.com/icza/gox v0.0.0-20230924165045-adcb03233bb5/go.mod h1:VbcN86fRkkUMPX2ufM85Um8zFndLZswoIW1eYtpAcVk=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/keys-pub/go-libfido2 v1.5.3 h1:vtgHxlSB43u6lj0TSuA3VvT6z3E7VI+L1a2hvMFdECk=
|
||||
github.com/keys-pub/go-libfido2 v1.5.3/go.mod h1:P0V19qHwJNY0htZwZDe9Ilvs/nokGhdFX7faKFyZ6+U=
|
||||
github.com/keybase/backoff v1.0.1-0.20160517061000-726b63b835ec h1:D6qL2WCnAuxucGbmL+mDW8IKRK1pex+R1fw5rKa9nXc=
|
||||
github.com/keybase/backoff v1.0.1-0.20160517061000-726b63b835ec/go.mod h1:jeBKj+20GIDry3doFsAMYH9n7Y3l7ajE3xJrKvVB23s=
|
||||
github.com/keybase/client/go v0.0.0-20240202160538-668db6be75e4 h1:MPUIAszF69Y8gZicAYxzDfrL5G3z/aKyfag8nPSq3js=
|
||||
github.com/keybase/client/go v0.0.0-20240202160538-668db6be75e4/go.mod h1:V3kb71GXhlWkJjB906M5xyiCqO7ygb4R6peCA+CMHtw=
|
||||
github.com/keybase/clockwork v0.1.1-0.20161209210251-976f45f4a979 h1:WABVkjKJ3UjbSTgGayemkXfUyZrDwFShivsoIikbM3c=
|
||||
github.com/keybase/clockwork v0.1.1-0.20161209210251-976f45f4a979/go.mod h1:2j97e0ZjlWYV7dDdV8BjKwMUmBbXu6zZF8FAa9gXRss=
|
||||
github.com/keybase/go-codec v0.0.0-20180928230036-164397562123 h1:yg56lYPqh9suJepqxOMd/liFgU/x+maRPiB30JNYykM=
|
||||
github.com/keybase/go-codec v0.0.0-20180928230036-164397562123/go.mod h1:r/eVVWCngg6TsFV/3HuS9sWhDkAzGG8mXhiuYA+Z/20=
|
||||
github.com/keybase/go-framed-msgpack-rpc v0.0.0-20230103225103-1f052922b096 h1:rMDGkwIszgGP7HodB/YdMVT39mMI5s+LUI6DOrJO0DE=
|
||||
github.com/keybase/go-framed-msgpack-rpc v0.0.0-20230103225103-1f052922b096/go.mod h1:XO67nMjltHJ8OsBWnFiDU1F67wR+rtJB21NXtb1TKyA=
|
||||
github.com/keybase/go-jsonw v0.0.0-20200325173637-df90f282c233 h1:zLk+cB/0ShMCBcgBOXYgellLZiZahXFicJleKyrlqiM=
|
||||
github.com/keybase/go-jsonw v0.0.0-20200325173637-df90f282c233/go.mod h1:lofKQwj13L0/7ji5VYaY0257JDlQE2BRRf+rI2Vk1rU=
|
||||
github.com/keybase/go-logging v0.0.0-20231213204715-4b3ff33ba5b6 h1:H4IvZdHXpeK963LgCMbTcEviEal4891UGf2iOqOGL94=
|
||||
github.com/keybase/go-logging v0.0.0-20231213204715-4b3ff33ba5b6/go.mod h1:0yOEB+QF1Ega1Cr7oMKb3yUAc3C9/eg6fBHB5HLP7AA=
|
||||
github.com/keybase/msgpackzip v0.0.0-20221220225959-4abf538d2b9c h1:PRG2AXSelSy7MiDI+PwJR2QSqI1N3OybRUutsMiHtpo=
|
||||
github.com/keybase/msgpackzip v0.0.0-20221220225959-4abf538d2b9c/go.mod h1:DkylHDco/FLr1+GM6wg0GF4E3CCKov54MSYojKYAbS0=
|
||||
github.com/keys-pub/go-libfido2 v1.5.4-0.20230628153049-536daffdd394 h1:zf+3yRJH5NIVOhLceS4P6AVQWEgQPhMCpxgMSB46HdI=
|
||||
github.com/keys-pub/go-libfido2 v1.5.4-0.20230628153049-536daffdd394/go.mod h1:92J9LtSBl0UyUWljElJpTbMMNhC6VeY8dshsu40qjjo=
|
||||
github.com/lox/go-touchid v0.0.0-20170712105233-619cc8e578d0 h1:m81erW+1MD5vl3lKQ/+TYPHJ6Y9/C1COqxXPE51FkDk=
|
||||
github.com/lox/go-touchid v0.0.0-20170712105233-619cc8e578d0/go.mod h1:EHbIQzfC3kdWFI81pLOFjssnolF+ALfmVf8PUdWBxo4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
@ -43,12 +65,14 @@ github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qd
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM=
|
||||
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl+f5AQ93FMcadG19WkS7MO6TXFOSas=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
@ -58,9 +82,10 @@ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyh
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
|
||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
|
||||
github.com/tink-crypto/tink-go/v2 v2.1.0 h1:QXFBguwMwTIaU17EgZpEJWsUSc60b1BAGTzBIoMdmok=
|
||||
github.com/tink-crypto/tink-go/v2 v2.1.0/go.mod h1:y1TnYFt1i2eZVfx4OGc+C+EMp4CoKWAw2VSEuoicHHI=
|
||||
github.com/twpayne/go-pinentry v0.3.0 h1:Rr+fEOZXmeItOb4thjeVaBWJKB9Xa/eojolycyF/26c=
|
||||
@ -73,20 +98,25 @@ golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE=
|
||||
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg=
|
||||
|
1
gui/__init__.py
Normal file
1
gui/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
@ -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
|
@ -36,6 +36,7 @@
|
||||
<developer_name>Bernd Schoolmann</developer_name>
|
||||
<update_contact>mail@quexten.com</update_contact>
|
||||
<releases>
|
||||
<release version="0.2.11" date="2024-02-17"/>
|
||||
<release version="0.2.9" date="2024-01-04"/>
|
||||
<release version="0.2.7" date="2023-12-30"/>
|
||||
<release version="0.2.6" date="2023-12-30"/>
|
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
|
53
gui/src/gui/browserbiometrics.py
Normal file
53
gui/src/gui/browserbiometrics.py
Normal file
@ -0,0 +1,53 @@
|
||||
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
|
||||
from . import components
|
||||
|
||||
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)
|
||||
|
||||
# vertical box
|
||||
self.box = Gtk.Box()
|
||||
self.box.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.set_child(self.box)
|
||||
|
||||
self.stack = Gtk.Stack()
|
||||
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
|
||||
self.box.append(self.stack)
|
||||
|
||||
self.preferences_page = Adw.PreferencesPage()
|
||||
self.preferences_page.set_title("General")
|
||||
self.stack.add_named(self.preferences_page, "preferences_page")
|
||||
|
||||
self.register_browser_biometrics_group = Adw.PreferencesGroup()
|
||||
self.register_browser_biometrics_group.set_title("Register Browser Biometrics")
|
||||
self.register_browser_biometrics_group.set_description("Run the following command in your terminal to set up the browser biometrics integration")
|
||||
self.preferences_page.add(self.register_browser_biometrics_group)
|
||||
|
||||
self.setup_command_row = Adw.ActionRow()
|
||||
self.setup_command_row.set_subtitle("flatpak run --filesystem=home --command=goldwarden com.quexten.Goldwarden setup browserbiometrics")
|
||||
self.setup_command_row.set_subtitle_selectable(True)
|
||||
self.register_browser_biometrics_group.add(self.setup_command_row)
|
||||
|
||||
self.set_default_size(700, 400)
|
||||
self.set_title("Goldwarden Browser Biometrics Setup")
|
||||
|
||||
app = MyApp(application_id="com.quexten.Goldwarden.browserbiometrics")
|
||||
app.run(sys.argv)
|
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)
|
178
gui/src/gui/quickaccess.py
Normal file
178
gui/src/gui/quickaccess.py
Normal file
@ -0,0 +1,178 @@
|
||||
import gi
|
||||
gi.require_version('Gtk', '4.0')
|
||||
gi.require_version('Adw', '1')
|
||||
gi.require_version('Notify', '0.7')
|
||||
import gc
|
||||
import time
|
||||
from gi.repository import Gtk, Adw, GLib, Notify, Gdk
|
||||
from ..services import goldwarden
|
||||
from threading import Thread
|
||||
import sys
|
||||
import os
|
||||
from ..services import totp
|
||||
Notify.init("Goldwarden")
|
||||
|
||||
# read line from stdin
|
||||
token = sys.stdin.readline()
|
||||
goldwarden.create_authenticated_connection(token)
|
||||
|
||||
def autotype(text):
|
||||
goldwarden.autotype(text)
|
||||
time.sleep(0.1)
|
||||
os._exit(0)
|
||||
|
||||
def set_clipboard(text):
|
||||
Gdk.Display.get_clipboard(Gdk.Display.get_default()).set_content(
|
||||
Gdk.ContentProvider.new_for_value(text)
|
||||
)
|
||||
|
||||
def kill():
|
||||
time.sleep(0.5)
|
||||
os._exit(0)
|
||||
thread = Thread(target=kill)
|
||||
thread.start()
|
||||
|
||||
class MyApp(Adw.Application):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('activate', self.on_activate)
|
||||
|
||||
def update_logins(self):
|
||||
logins = goldwarden.get_vault_logins()
|
||||
if logins == None:
|
||||
os._exit(0)
|
||||
return
|
||||
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):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.stack = Gtk.Stack()
|
||||
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
|
||||
self.set_child(self.stack)
|
||||
|
||||
self.box = Gtk.Box()
|
||||
self.box.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.stack.add_named(self.box, "box")
|
||||
|
||||
self.text_view = Adw.EntryRow()
|
||||
self.text_view.set_title("Search")
|
||||
|
||||
def on_type(entry):
|
||||
if len(entry.get_text()) > 1:
|
||||
self.results_list.show()
|
||||
else:
|
||||
self.results_list.hide()
|
||||
|
||||
while self.results_list.get_first_child() != None:
|
||||
self.results_list.remove(self.results_list.get_first_child())
|
||||
|
||||
self.filtered_logins = list(filter(lambda i: entry.get_text().lower() in i["name"].lower(), self.logins))
|
||||
if len( self.filtered_logins) > 10:
|
||||
self.filtered_logins = self.filtered_logins[0:10]
|
||||
self.starts_with_logins = list(filter(lambda i: i["name"].lower().startswith(entry.get_text().lower()), self.logins))
|
||||
self.other_logins = list(filter(lambda i: i not in self.starts_with_logins , self.filtered_logins))
|
||||
self.filtered_logins = None
|
||||
|
||||
for i in self.starts_with_logins + self.other_logins :
|
||||
action_row = Adw.ActionRow()
|
||||
action_row.set_title(i["name"])
|
||||
action_row.set_subtitle(i["username"])
|
||||
action_row.set_icon_name("dialog-password")
|
||||
action_row.set_activatable(True)
|
||||
action_row.password = i["password"]
|
||||
action_row.username = i["username"]
|
||||
action_row.uuid = i["uuid"]
|
||||
action_row.uri = i["uri"]
|
||||
action_row.totp = i["totp"]
|
||||
self.results_list.append(action_row)
|
||||
self.starts_with_logins = None
|
||||
self.other_logins = None
|
||||
self.text_view.connect("changed", lambda entry: on_type(entry))
|
||||
self.box.append(self.text_view)
|
||||
|
||||
self.results_list = Gtk.ListBox()
|
||||
# margin'
|
||||
self.results_list.set_margin_start(10)
|
||||
self.results_list.set_margin_end(10)
|
||||
self.results_list.set_margin_top(10)
|
||||
self.results_list.set_margin_bottom(10)
|
||||
self.results_list.hide()
|
||||
|
||||
keycont = Gtk.EventControllerKey()
|
||||
def handle_keypress(cotroller, keyval, keycode, state, user_data):
|
||||
ctrl_pressed = state & Gdk.ModifierType.CONTROL_MASK > 0
|
||||
alt_pressed = state & Gdk.ModifierType.ALT_MASK > 0
|
||||
|
||||
if keycode == 9:
|
||||
os._exit(0)
|
||||
|
||||
if keyval == 65364:
|
||||
# focus results
|
||||
if self.results_list.get_first_child() != None:
|
||||
self.results_list.get_first_child().grab_focus()
|
||||
self.results_list.select_row(self.results_list.get_first_child())
|
||||
|
||||
if keyval == 113:
|
||||
return False
|
||||
|
||||
if keycode == 36:
|
||||
self.hide()
|
||||
autotypeThread = Thread(target=autotype, args=(f"{self.results_list.get_selected_row().username}\t{self.results_list.get_selected_row().password}",))
|
||||
autotypeThread.start()
|
||||
if keyval == 112:
|
||||
print("pass", ctrl_pressed, alt_pressed)
|
||||
if ctrl_pressed and not alt_pressed:
|
||||
set_clipboard(self.results_list.get_selected_row().password)
|
||||
if ctrl_pressed and alt_pressed:
|
||||
self.hide()
|
||||
autotypeThread = Thread(target=autotype, args=(self.results_list.get_selected_row().password,))
|
||||
autotypeThread.start()
|
||||
elif keyval == 117:
|
||||
if ctrl_pressed and not alt_pressed:
|
||||
set_clipboard(self.results_list.get_selected_row().username)
|
||||
if ctrl_pressed and alt_pressed:
|
||||
self.hide()
|
||||
autotypeThread = Thread(target=autotype, args=(self.results_list.get_selected_row().username,))
|
||||
autotypeThread.start()
|
||||
elif keyval == 118:
|
||||
if ctrl_pressed and alt_pressed:
|
||||
environment = goldwarden.get_environment()
|
||||
if environment == None:
|
||||
return
|
||||
item_uri = environment["vault"] + "#/vault?itemId=" + self.results_list.get_selected_row().uuid
|
||||
Gtk.show_uri(None, item_uri, Gdk.CURRENT_TIME)
|
||||
elif keyval == 108:
|
||||
if ctrl_pressed and alt_pressed:
|
||||
Gtk.show_uri(None, self.results_list.get_selected_row().uri, Gdk.CURRENT_TIME)
|
||||
elif keyval == 116:
|
||||
totp_code = totp.totp(self.resuts_list.get_selected_row().totp)
|
||||
if ctrl_pressed and not alt_pressed:
|
||||
set_clipboard(totp_code)
|
||||
if ctrl_pressed and alt_pressed:
|
||||
self.hide()
|
||||
autotypeThread = Thread(target=autotype, args=(totp_code,))
|
||||
autotypeThread.start()
|
||||
elif keyval == 102:
|
||||
# focus search
|
||||
self.text_view.grab_focus()
|
||||
|
||||
keycont.connect('key-pressed', handle_keypress, self)
|
||||
self.add_controller(keycont)
|
||||
|
||||
self.results_list.get_style_context().add_class("boxed-list")
|
||||
self.box.append(self.results_list)
|
||||
self.set_default_size(700, 700)
|
||||
self.set_title("Goldwarden Quick Access")
|
||||
|
||||
app = MyApp(application_id="com.quexten.Goldwarden.autofill-menu")
|
||||
app.run(sys.argv)
|
@ -5,13 +5,46 @@ gi.require_version('Gtk', '4.0')
|
||||
gi.require_version('Adw', '1')
|
||||
import gc
|
||||
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk
|
||||
import goldwarden
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk, Gio
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
def shortcuts_button_clicked():
|
||||
p = subprocess.Popen(["python3", "-m", "src.gui.shortcuts"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
|
||||
p.stdin.write(f"{token}\n".encode())
|
||||
p.stdin.flush()
|
||||
|
||||
def ssh_button_clicked():
|
||||
p = subprocess.Popen(["python3", "-m", "src.gui.ssh"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
|
||||
p.stdin.write(f"{token}\n".encode())
|
||||
p.stdin.flush()
|
||||
|
||||
def browserbiometrics_button_clicked():
|
||||
p = subprocess.Popen(["python3", "-m", "src.gui.browserbiometrics"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
|
||||
p.stdin.write(f"{token}\n".encode())
|
||||
p.stdin.flush()
|
||||
|
||||
def add_action_row(parent, title, subtitle, icon=None):
|
||||
row = Adw.ActionRow()
|
||||
row.set_title(title)
|
||||
row.set_subtitle(subtitle)
|
||||
if icon != None:
|
||||
row.set_icon_name(icon)
|
||||
parent.add(row)
|
||||
return row
|
||||
|
||||
class SettingsWinvdow(Gtk.ApplicationWindow):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -39,103 +72,15 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
|
||||
self.preferences_page.set_title("General")
|
||||
self.stack.add_named(self.preferences_page, "preferences_page")
|
||||
|
||||
self.preferences_group = Adw.PreferencesGroup()
|
||||
self.preferences_group.set_title("Services")
|
||||
self.preferences_page.add(self.preferences_group)
|
||||
|
||||
self.ssh_row = Adw.ActionRow()
|
||||
self.ssh_row.set_title("SSH Daemon")
|
||||
self.ssh_row.set_subtitle("Getting status...")
|
||||
self.ssh_row.set_icon_name("emblem-default")
|
||||
self.preferences_group.add(self.ssh_row)
|
||||
|
||||
self.goldwarden_daemon_row = Adw.ActionRow()
|
||||
self.goldwarden_daemon_row.set_title("Goldwarden Daemon")
|
||||
self.goldwarden_daemon_row.set_subtitle("Getting status...")
|
||||
self.goldwarden_daemon_row.set_icon_name("emblem-default")
|
||||
self.preferences_group.add(self.goldwarden_daemon_row)
|
||||
|
||||
self.login_with_device = Adw.ActionRow()
|
||||
self.login_with_device.set_title("Login with device")
|
||||
self.login_with_device.set_subtitle("Waiting for requests...")
|
||||
self.preferences_group.add(self.login_with_device)
|
||||
|
||||
self.status_row = Adw.ActionRow()
|
||||
self.status_row.set_title("DBUS Service")
|
||||
self.status_row.set_subtitle("Listening")
|
||||
self.preferences_group.add(self.status_row)
|
||||
|
||||
self.shortcut_preferences_group = Adw.PreferencesGroup()
|
||||
self.shortcut_preferences_group.set_title("Shortcuts")
|
||||
self.preferences_page.add(self.shortcut_preferences_group)
|
||||
|
||||
self.autofill_row = Adw.ActionRow()
|
||||
self.autofill_row.set_title("Autofill Shortcut")
|
||||
self.autofill_row.set_subtitle("Unavailable, please set up a shortcut in your desktop environment (README)")
|
||||
self.shortcut_preferences_group.add(self.autofill_row)
|
||||
|
||||
self.autofill_icon = components.StatusIcon()
|
||||
self.autofill_icon.set_icon("dialog-warning", "warning")
|
||||
self.autofill_row.add_prefix(self.autofill_icon)
|
||||
|
||||
self.copy_username_shortcut_row = Adw.ActionRow()
|
||||
self.copy_username_shortcut_row.set_title("Copy Username Shortcut")
|
||||
self.copy_username_shortcut_row.set_subtitle("U")
|
||||
self.shortcut_preferences_group.add(self.copy_username_shortcut_row)
|
||||
|
||||
self.copy_password_shortcut_row = Adw.ActionRow()
|
||||
self.copy_password_shortcut_row.set_title("Copy Password Shortcut")
|
||||
self.copy_password_shortcut_row.set_subtitle("P")
|
||||
self.shortcut_preferences_group.add(self.copy_password_shortcut_row)
|
||||
|
||||
self.vault_status_preferences_group = Adw.PreferencesGroup()
|
||||
self.vault_status_preferences_group.set_title("Vault Status")
|
||||
self.preferences_page.add(self.vault_status_preferences_group)
|
||||
|
||||
self.status_row = Adw.ActionRow()
|
||||
self.status_row.set_title("Vault Status")
|
||||
self.status_row.set_subtitle("Locked")
|
||||
self.vault_status_preferences_group.add(self.status_row)
|
||||
|
||||
self.vault_status_icon = components.StatusIcon()
|
||||
self.vault_status_icon.set_icon("dialog-error", "error")
|
||||
self.status_row.add_prefix(self.vault_status_icon)
|
||||
|
||||
self.last_sync_row = Adw.ActionRow()
|
||||
self.last_sync_row.set_title("Last Sync")
|
||||
self.last_sync_row.set_subtitle("Never")
|
||||
self.last_sync_row.set_icon_name("emblem-synchronizing-symbolic")
|
||||
self.vault_status_preferences_group.add(self.last_sync_row)
|
||||
|
||||
self.websocket_connected_row = Adw.ActionRow()
|
||||
self.websocket_connected_row.set_title("Websocket Connected")
|
||||
self.websocket_connected_row.set_subtitle("False")
|
||||
self.vault_status_preferences_group.add(self.websocket_connected_row)
|
||||
|
||||
self.websocket_connected_status_icon = components.StatusIcon()
|
||||
self.websocket_connected_status_icon.set_icon("dialog-error", "error")
|
||||
self.websocket_connected_row.add_prefix(self.websocket_connected_status_icon)
|
||||
|
||||
self.login_row = Adw.ActionRow()
|
||||
self.login_row.set_title("Vault Login Entries")
|
||||
self.login_row.set_subtitle("0")
|
||||
self.login_row.set_icon_name("dialog-password-symbolic")
|
||||
self.vault_status_preferences_group.add(self.login_row)
|
||||
|
||||
self.notes_row = Adw.ActionRow()
|
||||
self.notes_row.set_title("Vault Notes")
|
||||
self.notes_row.set_subtitle("0")
|
||||
self.notes_row.set_icon_name("emblem-documents-symbolic")
|
||||
self.vault_status_preferences_group.add(self.notes_row)
|
||||
|
||||
self.action_preferences_group = Adw.PreferencesGroup()
|
||||
self.action_preferences_group.set_title("Actions")
|
||||
self.preferences_page.add(self.action_preferences_group)
|
||||
|
||||
self.autotype_button = Gtk.Button()
|
||||
self.autotype_button.set_label("Autotype")
|
||||
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))
|
||||
|
||||
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)
|
||||
|
||||
@ -178,16 +123,59 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
|
||||
self.wiki_button.set_label("Help & Wiki")
|
||||
self.wiki_button.set_margin_top(10)
|
||||
self.action_preferences_group.add(self.wiki_button)
|
||||
|
||||
self.vault_status_preferences_group = Adw.PreferencesGroup()
|
||||
self.vault_status_preferences_group.set_title("Vault Status")
|
||||
self.preferences_page.add(self.vault_status_preferences_group)
|
||||
|
||||
self.status_row = add_action_row(self.vault_status_preferences_group, "Vault Status", "Locked")
|
||||
|
||||
self.vault_status_icon = components.StatusIcon()
|
||||
self.vault_status_icon.set_icon("dialog-error", "error")
|
||||
self.status_row.add_prefix(self.vault_status_icon)
|
||||
|
||||
self.last_sync_row = add_action_row(self.vault_status_preferences_group, "Last Sync", "Never", "emblem-synchronizing-symbolic")
|
||||
self.websocket_connected_row = add_action_row(self.vault_status_preferences_group, "Websocket Connected", "False")
|
||||
|
||||
self.websocket_connected_status_icon = components.StatusIcon()
|
||||
self.websocket_connected_status_icon.set_icon("dialog-error", "error")
|
||||
self.websocket_connected_row.add_prefix(self.websocket_connected_status_icon)
|
||||
|
||||
self.login_row = add_action_row(self.vault_status_preferences_group, "Vault Login Entries", "0", "dialog-password-symbolic")
|
||||
self.notes_row = add_action_row(self.vault_status_preferences_group, "Vault Notes", "0", "emblem-documents-symbolic")
|
||||
|
||||
self.header = Gtk.HeaderBar()
|
||||
self.set_titlebar(self.header)
|
||||
|
||||
action = Gio.SimpleAction.new("shortcuts", None)
|
||||
action.connect("activate", lambda action, parameter: shortcuts_button_clicked())
|
||||
self.add_action(action)
|
||||
menu = Gio.Menu.new()
|
||||
menu.append("Keyboard Shortcuts", "win.shortcuts")
|
||||
self.popover = Gtk.PopoverMenu()
|
||||
self.popover.set_menu_model(menu)
|
||||
|
||||
action = Gio.SimpleAction.new("ssh", None)
|
||||
action.connect("activate", lambda action, parameter: ssh_button_clicked())
|
||||
self.add_action(action)
|
||||
menu.append("SSH Agent", "win.ssh")
|
||||
|
||||
action = Gio.SimpleAction.new("browserbiometrics", None)
|
||||
action.connect("activate", lambda action, parameter: browserbiometrics_button_clicked())
|
||||
self.add_action(action)
|
||||
menu.append("Browser Biometrics", "win.browserbiometrics")
|
||||
|
||||
self.hamburger = Gtk.MenuButton()
|
||||
self.hamburger.set_popover(self.popover)
|
||||
self.hamburger.set_icon_name("open-menu-symbolic")
|
||||
self.header.pack_start(self.hamburger)
|
||||
|
||||
|
||||
def update_labels():
|
||||
GLib.timeout_add(1000, update_labels)
|
||||
|
||||
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"])
|
||||
self.goldwarden_daemon_row.set_subtitle("Listening at "+runtimeCfg["goldwardenSocketPath"])
|
||||
|
||||
if status != None:
|
||||
if pin_set:
|
||||
@ -198,15 +186,11 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
|
||||
self.banner.set_revealed(True)
|
||||
logged_in = status["loggedIn"]
|
||||
if logged_in and not status["locked"]:
|
||||
self.preferences_group.set_visible(True)
|
||||
self.shortcut_preferences_group.set_visible(True)
|
||||
self.autotype_button.set_visible(True)
|
||||
self.login_row.set_sensitive(True)
|
||||
self.notes_row.set_sensitive(True)
|
||||
self.websocket_connected_row.set_sensitive(True)
|
||||
else:
|
||||
self.preferences_group.set_visible(False)
|
||||
self.shortcut_preferences_group.set_visible(False)
|
||||
self.autotype_button.set_visible(False)
|
||||
self.websocket_connected_row.set_sensitive(False)
|
||||
self.login_row.set_sensitive(False)
|
||||
@ -241,16 +225,13 @@ class SettingsWinvdow(Gtk.ApplicationWindow):
|
||||
if not is_daemon_running:
|
||||
self.status_row.set_subtitle("Daemon not running")
|
||||
self.vault_status_icon.set_icon("dialog-error", "error")
|
||||
|
||||
GLib.timeout_add(5000, update_labels)
|
||||
|
||||
GLib.timeout_add(1000, update_labels)
|
||||
self.set_default_size(400, 700)
|
||||
self.set_title("Goldwarden")
|
||||
|
||||
|
||||
#add title buttons
|
||||
self.title_bar = Gtk.HeaderBar()
|
||||
self.set_titlebar(self.title_bar)
|
||||
|
||||
class MyApp(Adw.Application):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@ -262,33 +243,13 @@ class MyApp(Adw.Application):
|
||||
|
||||
def show_login():
|
||||
dialog = Gtk.Dialog(title="Goldwarden")
|
||||
preference_group = Adw.PreferencesGroup()
|
||||
preference_group.set_title("Config")
|
||||
preference_group.set_margin_top(10)
|
||||
preference_group.set_margin_bottom(10)
|
||||
preference_group.set_margin_start(10)
|
||||
preference_group.set_margin_end(10)
|
||||
|
||||
dialog.get_content_area().append(preference_group)
|
||||
|
||||
api_url_entry = Adw.EntryRow()
|
||||
api_url_entry.set_title("API Url")
|
||||
# set value
|
||||
api_url_entry.set_text("https://vault.bitwarden.com/api")
|
||||
preference_group.add(api_url_entry)
|
||||
|
||||
identity_url_entry = Adw.EntryRow()
|
||||
identity_url_entry.set_title("Identity Url")
|
||||
identity_url_entry.set_text("https://vault.bitwarden.com/identity")
|
||||
preference_group.add(identity_url_entry)
|
||||
|
||||
notification_url_entry = Adw.EntryRow()
|
||||
notification_url_entry.set_title("Notification URL")
|
||||
notification_url_entry.set_text("https://notifications.bitwarden.com/")
|
||||
preference_group.add(notification_url_entry)
|
||||
|
||||
auth_preference_group = Adw.PreferencesGroup()
|
||||
auth_preference_group.set_title("Authentication")
|
||||
auth_preference_group.set_margin_top(10)
|
||||
auth_preference_group.set_margin_bottom(10)
|
||||
auth_preference_group.set_margin_start(10)
|
||||
auth_preference_group.set_margin_end(10)
|
||||
dialog.get_content_area().append(auth_preference_group)
|
||||
|
||||
email_entry = Adw.EntryRow()
|
||||
@ -310,9 +271,7 @@ def show_login():
|
||||
def on_save(res):
|
||||
if res != Gtk.ResponseType.OK:
|
||||
return
|
||||
goldwarden.set_api_url(api_url_entry.get_text())
|
||||
goldwarden.set_identity_url(identity_url_entry.get_text())
|
||||
goldwarden.set_notification_url(notification_url_entry.get_text())
|
||||
goldwarden.set_url(url_entry.get_text())
|
||||
goldwarden.set_client_id(client_id_entry.get_text())
|
||||
goldwarden.set_client_secret(client_secret_entry.get_text())
|
||||
def login():
|
||||
@ -329,6 +288,20 @@ def show_login():
|
||||
login_thread = Thread(target=login)
|
||||
login_thread.start()
|
||||
|
||||
preference_group = Adw.PreferencesGroup()
|
||||
preference_group.set_title("Config")
|
||||
preference_group.set_margin_top(10)
|
||||
preference_group.set_margin_bottom(10)
|
||||
preference_group.set_margin_start(10)
|
||||
preference_group.set_margin_end(10)
|
||||
|
||||
dialog.get_content_area().append(preference_group)
|
||||
|
||||
url_entry = Adw.EntryRow()
|
||||
url_entry.set_title("Base Url")
|
||||
url_entry.set_text("https://vault.bitwarden.com/")
|
||||
preference_group.add(url_entry)
|
||||
|
||||
#ok response
|
||||
dialog.connect("response", lambda dialog, response: on_save(response))
|
||||
dialog.set_default_size(400, 200)
|
||||
@ -345,6 +318,5 @@ Gtk.StyleContext.add_provider_for_display(
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
)
|
||||
|
||||
|
||||
app = MyApp(application_id="com.quexten.Goldwarden.settings")
|
||||
app.run(sys.argv)
|
80
gui/src/gui/shortcuts.py
Normal file
80
gui/src/gui/shortcuts.py
Normal file
@ -0,0 +1,80 @@
|
||||
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
|
||||
from . import components
|
||||
|
||||
def add_action_row(parent, title, subtitle, icon=None):
|
||||
row = Adw.ActionRow()
|
||||
row.set_title(title)
|
||||
row.set_subtitle(subtitle)
|
||||
if icon != None:
|
||||
row.set_icon_name(icon)
|
||||
parent.add(row)
|
||||
return row
|
||||
|
||||
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)
|
||||
|
||||
# vertical box
|
||||
self.box = Gtk.Box()
|
||||
self.box.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.set_child(self.box)
|
||||
|
||||
self.stack = Gtk.Stack()
|
||||
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
|
||||
self.box.append(self.stack)
|
||||
|
||||
self.preferences_page = Adw.PreferencesPage()
|
||||
self.preferences_page.set_title("General")
|
||||
self.stack.add_named(self.preferences_page, "preferences_page")
|
||||
|
||||
self.global_preferences_group = Adw.PreferencesGroup()
|
||||
self.global_preferences_group.set_title("Global Shortcuts")
|
||||
self.preferences_page.add(self.global_preferences_group)
|
||||
|
||||
self.autofill_row = Adw.ActionRow()
|
||||
self.autofill_row.set_title("Autofill Shortcut")
|
||||
self.autofill_row.set_subtitle("Not implemented - check the wiki for manual setup")
|
||||
self.global_preferences_group.add(self.autofill_row)
|
||||
|
||||
self.autofill_icon = components.StatusIcon()
|
||||
self.autofill_icon.set_icon("dialog-warning", "warning")
|
||||
self.autofill_row.add_prefix(self.autofill_icon)
|
||||
|
||||
self.quickaccess_preferences_group = Adw.PreferencesGroup()
|
||||
self.quickaccess_preferences_group.set_title("Quick Access Shortcuts")
|
||||
self.preferences_page.add(self.quickaccess_preferences_group)
|
||||
|
||||
add_action_row(self.quickaccess_preferences_group, "Copy Username Shortcut", "CTRL + U")
|
||||
add_action_row(self.quickaccess_preferences_group, "Autotype Username Shortcut", "CTRL + ALT + U")
|
||||
add_action_row(self.quickaccess_preferences_group, "Copy Password Shortcut", "CTRL + P")
|
||||
add_action_row(self.quickaccess_preferences_group, "Autotype Password Shortcut", "CTRL + ALT + P")
|
||||
add_action_row(self.quickaccess_preferences_group, "Copy TOTP Shortcut", "CTRL + T")
|
||||
add_action_row(self.quickaccess_preferences_group, "Autotype TOTP Shortcut", "CTRL + ALT + T")
|
||||
add_action_row(self.quickaccess_preferences_group, "Launch URI Shortcut", "CTRL+L")
|
||||
add_action_row(self.quickaccess_preferences_group, "Launch Web Vault Shortcut", "CTRL+V")
|
||||
add_action_row(self.quickaccess_preferences_group, "Focus Search Shortcut", "F")
|
||||
add_action_row(self.quickaccess_preferences_group, "Quit Shortcut", "Esc")
|
||||
|
||||
self.set_default_size(700, 700)
|
||||
self.set_title("Goldwarden Shortcuts")
|
||||
|
||||
app = MyApp(application_id="com.quexten.Goldwarden.shortcuts")
|
||||
app.run(sys.argv)
|
67
gui/src/gui/ssh.py
Normal file
67
gui/src/gui/ssh.py
Normal file
@ -0,0 +1,67 @@
|
||||
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
|
||||
from . import components
|
||||
|
||||
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)
|
||||
|
||||
# vertical box
|
||||
self.box = Gtk.Box()
|
||||
self.box.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.set_child(self.box)
|
||||
|
||||
self.stack = Gtk.Stack()
|
||||
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
|
||||
self.box.append(self.stack)
|
||||
|
||||
self.preferences_page = Adw.PreferencesPage()
|
||||
self.preferences_page.set_title("General")
|
||||
self.stack.add_named(self.preferences_page, "preferences_page")
|
||||
|
||||
self.add_ssh_key_group = Adw.PreferencesGroup()
|
||||
self.add_ssh_key_group.set_title("Add an SSH Key")
|
||||
self.preferences_page.add(self.add_ssh_key_group)
|
||||
|
||||
self.add_ssh_key_row = Adw.ActionRow()
|
||||
self.add_ssh_key_row.set_subtitle("flatpak run --command=goldwarden com.quexten.Goldwarden ssh add --name MY_KEY_NAME")
|
||||
self.add_ssh_key_row.set_subtitle_selectable(True)
|
||||
self.add_ssh_key_group.add(self.add_ssh_key_row)
|
||||
|
||||
self.ssh_socket_path_group = Adw.PreferencesGroup()
|
||||
self.ssh_socket_path_group.set_title("SSH Socket Path")
|
||||
self.ssh_socket_path_group.set_description("Add this to your your enviorment variables")
|
||||
self.preferences_page.add(self.ssh_socket_path_group)
|
||||
|
||||
self.ssh_socket_path_row = Adw.ActionRow()
|
||||
self.ssh_socket_path_row.set_subtitle("export SSH_AUTH_SOCK=/home/$USER/.var/app/com.quexten.Goldwarden/data/ssh-auth-sock")
|
||||
self.ssh_socket_path_row.set_subtitle_selectable(True)
|
||||
self.ssh_socket_path_group.add(self.ssh_socket_path_row)
|
||||
|
||||
self.git_signing_group = Adw.PreferencesGroup()
|
||||
self.git_signing_group.set_title("Git Signing")
|
||||
self.git_signing_group.set_description("Check the wiki for more information")
|
||||
self.preferences_page.add(self.git_signing_group)
|
||||
|
||||
self.set_default_size(400, 700)
|
||||
self.set_title("Goldwarden SSH Setup")
|
||||
|
||||
app = MyApp(application_id="com.quexten.Goldwarden.sshsetup")
|
||||
app.run(sys.argv)
|
@ -30,7 +30,7 @@ def request_autostart():
|
||||
'handle_token': GLib.Variant('s', f'com/quexten/Goldwarden/{token}'),
|
||||
'reason': GLib.Variant('s', ('Autostart Goldwarden in the background.')),
|
||||
'autostart': GLib.Variant('b', True),
|
||||
'commandline': GLib.Variant('as', ['main.py', '--hidden']),
|
||||
'commandline': GLib.Variant('as', ['goldwarden_ui_main.py', '--hidden']),
|
||||
'dbus-activatable': GLib.Variant('b', False),
|
||||
}
|
||||
|
56
gui/src/linux/main.py
Normal file
56
gui/src/linux/main.py
Normal file
@ -0,0 +1,56 @@
|
||||
#!/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:
|
||||
print("Enabling autostart...")
|
||||
subprocess.Popen(["python3", "-m", "src.linux.background"], cwd=root_path, start_new_session=True)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
while True:
|
||||
time.sleep(60)
|
@ -1,13 +1,19 @@
|
||||
#Python DBUS Test Server
|
||||
#runs until the Quit() method is called via DBUS
|
||||
|
||||
from gi import require_version
|
||||
require_version('Gtk', '4.0')
|
||||
require_version('Adw', '1')
|
||||
from gi.repository import Gtk
|
||||
import dbus
|
||||
import dbus.service
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from threading import Thread
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
on_autofill = lambda: None
|
||||
daemon_token = None
|
||||
root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, os.pardir))
|
||||
|
||||
class GoldwardenDBUSService(dbus.service.Object):
|
||||
def __init__(self):
|
||||
@ -16,11 +22,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()
|
193
gui/src/services/goldwarden.py
Normal file
193
gui/src/services/goldwarden.py
Normal file
@ -0,0 +1,193 @@
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
from shutil import which
|
||||
import sys
|
||||
|
||||
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)
|
||||
|
||||
# detect goldwarden binary
|
||||
BINARY_PATHS = [
|
||||
"/app/bin/goldwarden",
|
||||
"/usr/bin/goldwarden",
|
||||
str(Path.home()) + "/go/src/github.com/quexten/goldwarden/goldwarden"
|
||||
]
|
||||
|
||||
BINARY_PATH = None
|
||||
for path in BINARY_PATHS:
|
||||
if os.path.exists(path):
|
||||
BINARY_PATH = path
|
||||
break
|
||||
|
||||
if BINARY_PATH is None:
|
||||
BINARY_PATH = which('goldwarden')
|
||||
if isinstance(BINARY_PATH,str):
|
||||
BINARY_PATH = BINARY_PATH.strip()
|
||||
|
||||
if BINARY_PATH is None:
|
||||
print("goldwarden executable not found")
|
||||
sys.exit()
|
||||
|
||||
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 not "Logged in" in result:
|
||||
return "badpass"
|
||||
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(text):
|
||||
goldwarden_cmd = f"{BINARY_PATH} autotype"
|
||||
process = subprocess.Popen(goldwarden_cmd.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
text_hex = text.encode("utf-8").hex()
|
||||
process.stdin.write(text_hex + "\n")
|
||||
process.stdin.flush()
|
||||
process.wait()
|
||||
|
||||
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()
|
20
gui/src/services/totp.py
Normal file
20
gui/src/services/totp.py
Normal file
@ -0,0 +1,20 @@
|
||||
# https://github.com/susam/mintotp
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import base64
|
||||
import hmac
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
def hotp(key, counter, digits=6, digest='sha1'):
|
||||
key = base64.b32decode(key.upper() + '=' * ((8 - len(key)) % 8))
|
||||
counter = struct.pack('>Q', counter)
|
||||
mac = hmac.new(key, counter, digest).digest()
|
||||
offset = mac[-1] & 0x0f
|
||||
binary = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff
|
||||
return str(binary)[-digits:].zfill(digits)
|
||||
|
||||
|
||||
def totp(key, time_step=30, digits=6, digest='sha1'):
|
||||
return hotp(key, int(time.time() / time_step), digits, digest)
|
@ -14,6 +14,24 @@ type SetNotificationsURLRequest struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
type SetVaultURLRequest struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
type SetURLsAutomaticallyRequest struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
type GetConfigEnvironmentRequest struct {
|
||||
}
|
||||
|
||||
type GetConfigEnvironmentResponse struct {
|
||||
ApiURL string
|
||||
IdentityURL string
|
||||
NotificationsURL string
|
||||
VaultURL string
|
||||
}
|
||||
|
||||
type SetClientIDRequest struct {
|
||||
Value string
|
||||
}
|
||||
@ -93,4 +111,36 @@ func init() {
|
||||
}
|
||||
return req, nil
|
||||
}, SetClientSecretRequest{})
|
||||
|
||||
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
||||
var req SetVaultURLRequest
|
||||
err := json.Unmarshal(payload, &req)
|
||||
if err != nil {
|
||||
panic("Unmarshal: " + err.Error())
|
||||
}
|
||||
return req, nil
|
||||
}, SetVaultURLRequest{})
|
||||
|
||||
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
||||
var req SetURLsAutomaticallyRequest
|
||||
err := json.Unmarshal(payload, &req)
|
||||
if err != nil {
|
||||
panic("Unmarshal: " + err.Error())
|
||||
}
|
||||
return req, nil
|
||||
}, SetURLsAutomaticallyRequest{})
|
||||
|
||||
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
||||
var req GetConfigEnvironmentRequest
|
||||
return req, nil
|
||||
}, GetConfigEnvironmentRequest{})
|
||||
|
||||
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
||||
var req GetConfigEnvironmentResponse
|
||||
err := json.Unmarshal(payload, &req)
|
||||
if err != nil {
|
||||
panic("Unmarshal: " + err.Error())
|
||||
}
|
||||
return req, nil
|
||||
}, GetConfigEnvironmentResponse{})
|
||||
}
|
||||
|
@ -28,7 +28,8 @@ type DecryptedLoginCipher struct {
|
||||
UUID string
|
||||
OrgaizationID string
|
||||
Notes string
|
||||
TwoFactorCode string
|
||||
TOTPSeed string
|
||||
URI string
|
||||
}
|
||||
|
||||
type AddLoginRequest struct {
|
||||
|
72
ipc/messages/send.go
Normal file
72
ipc/messages/send.go
Normal file
@ -0,0 +1,72 @@
|
||||
package messages
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type GetSendRequest struct {
|
||||
Name string
|
||||
Text string
|
||||
}
|
||||
|
||||
type GetSendResponse struct {
|
||||
Found bool
|
||||
Text string
|
||||
}
|
||||
|
||||
type CreateSendRequest struct {
|
||||
Name string
|
||||
Text string
|
||||
}
|
||||
|
||||
type CreateSendResponse struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
type ListSendsRequest struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
||||
var req GetSendRequest
|
||||
err := json.Unmarshal(payload, &req)
|
||||
if err != nil {
|
||||
panic("Unmarshal: " + err.Error())
|
||||
}
|
||||
return req, nil
|
||||
}, GetSendRequest{})
|
||||
|
||||
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
||||
var req GetSendResponse
|
||||
err := json.Unmarshal(payload, &req)
|
||||
if err != nil {
|
||||
panic("Unmarshal: " + err.Error())
|
||||
}
|
||||
return req, nil
|
||||
}, GetSendResponse{})
|
||||
|
||||
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
||||
var req CreateSendRequest
|
||||
err := json.Unmarshal(payload, &req)
|
||||
if err != nil {
|
||||
panic("Unmarshal: " + err.Error())
|
||||
}
|
||||
return req, nil
|
||||
}, CreateSendRequest{})
|
||||
|
||||
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
||||
var req CreateSendResponse
|
||||
err := json.Unmarshal(payload, &req)
|
||||
if err != nil {
|
||||
panic("Unmarshal: " + err.Error())
|
||||
}
|
||||
return req, nil
|
||||
}, CreateSendResponse{})
|
||||
|
||||
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
||||
var req ListSendsRequest
|
||||
err := json.Unmarshal(payload, &req)
|
||||
if err != nil {
|
||||
panic("Unmarshal: " + err.Error())
|
||||
}
|
||||
return req, nil
|
||||
}, ListSendsRequest{})
|
||||
}
|
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{})
|
||||
}
|
60
main.go
60
main.go
@ -20,22 +20,18 @@ func main() {
|
||||
configPath = strings.ReplaceAll(configPath, "~", userHome)
|
||||
|
||||
runtimeConfig := config.RuntimeConfig{
|
||||
WebsocketDisabled: os.Getenv("GOLDWARDEN_WEBSOCKET_DISABLED") == "true",
|
||||
DisableSSHAgent: os.Getenv("GOLDWARDEN_SSH_AGENT_DISABLED") == "true",
|
||||
DisableAuth: os.Getenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED") == "true",
|
||||
DisablePinRequirement: os.Getenv("GOLDWARDEN_PIN_REQUIREMENT_DISABLED") == "true",
|
||||
DoNotPersistConfig: os.Getenv("GOLDWARDEN_DO_NOT_PERSIST_CONFIG") == "true",
|
||||
ApiURI: os.Getenv("GOLDWARDEN_API_URI"),
|
||||
IdentityURI: os.Getenv("GOLDWARDEN_IDENTITY_URI"),
|
||||
SingleProcess: os.Getenv("GOLDWARDEN_SINGLE_PROCESS") == "true",
|
||||
DeviceUUID: os.Getenv("GOLDWARDEN_DEVICE_UUID"),
|
||||
AuthMethod: os.Getenv("GOLDWARDEN_AUTH_METHOD"),
|
||||
User: os.Getenv("GOLDWARDEN_AUTH_USER"),
|
||||
Password: os.Getenv("GOLDWARDEN_AUTH_PASSWORD"),
|
||||
Pin: os.Getenv("GOLDWARDEN_PIN"),
|
||||
UseMemguard: os.Getenv("GOLDWARDEN_NO_MEMGUARD") != "true",
|
||||
SSHAgentSocketPath: os.Getenv("GOLDWARDEN_SSH_AUTH_SOCK"),
|
||||
GoldwardenSocketPath: os.Getenv("GOLDWARDEN_SOCKET_PATH"),
|
||||
WebsocketDisabled: os.Getenv("GOLDWARDEN_WEBSOCKET_DISABLED") == "true",
|
||||
DisableSSHAgent: os.Getenv("GOLDWARDEN_SSH_AGENT_DISABLED") == "true",
|
||||
DoNotPersistConfig: os.Getenv("GOLDWARDEN_DO_NOT_PERSIST_CONFIG") == "true",
|
||||
DeviceUUID: os.Getenv("GOLDWARDEN_DEVICE_UUID"),
|
||||
AuthMethod: os.Getenv("GOLDWARDEN_AUTH_METHOD"),
|
||||
User: os.Getenv("GOLDWARDEN_AUTH_USER"),
|
||||
Password: os.Getenv("GOLDWARDEN_AUTH_PASSWORD"),
|
||||
Pin: os.Getenv("GOLDWARDEN_PIN"),
|
||||
UseMemguard: os.Getenv("GOLDWARDEN_NO_MEMGUARD") != "true",
|
||||
SSHAgentSocketPath: os.Getenv("GOLDWARDEN_SSH_AUTH_SOCK"),
|
||||
GoldwardenSocketPath: os.Getenv("GOLDWARDEN_SOCKET_PATH"),
|
||||
DaemonAuthToken: os.Getenv("GOLDWARDEN_DAEMON_AUTH_TOKEN"),
|
||||
|
||||
ConfigDirectory: configPath,
|
||||
}
|
||||
@ -45,15 +41,18 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
if runtimeConfig.SSHAgentSocketPath == "" {
|
||||
runtimeConfig.SSHAgentSocketPath = home + "/.goldwarden-ssh-agent.sock"
|
||||
if _, err := os.Stat(home + "/.ssh-agent-socket"); err == nil {
|
||||
runtimeConfig.SSHAgentSocketPath = home + "/.ssh-agent-socket"
|
||||
} else if _, err := os.Stat(home + "/.var/app/com.quexten.Goldwarden/data/ssh-auth-sock"); err == nil {
|
||||
runtimeConfig.SSHAgentSocketPath = home + "/.var/app/com.quexten.Goldwarden/data/ssh-auth-sock"
|
||||
}
|
||||
}
|
||||
if runtimeConfig.GoldwardenSocketPath == "" {
|
||||
runtimeConfig.GoldwardenSocketPath = home + "/.goldwarden.sock"
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 && (strings.Contains(os.Args[1], "com.8bit.bitwarden.json") || strings.Contains(os.Args[1], "chrome-extension://")) {
|
||||
browserbiometrics.Main(&runtimeConfig)
|
||||
return
|
||||
if _, err := os.Stat(home + "/.goldwarden.sock"); err == nil {
|
||||
runtimeConfig.GoldwardenSocketPath = home + "/.goldwarden.sock"
|
||||
} else if _, err := os.Stat(home + "/.var/app/com.quexten.Goldwarden/data/goldwarden.sock"); err == nil {
|
||||
runtimeConfig.GoldwardenSocketPath = home + "/.var/app/com.quexten.Goldwarden/data/goldwarden.sock"
|
||||
}
|
||||
}
|
||||
|
||||
_, err = os.Stat("/.flatpak-info")
|
||||
@ -62,22 +61,13 @@ 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"
|
||||
}
|
||||
|
||||
if runtimeConfig.SingleProcess {
|
||||
runtimeConfig.DisablePinRequirement = true
|
||||
runtimeConfig.DisableAuth = true
|
||||
}
|
||||
|
||||
if runtimeConfig.DisablePinRequirement {
|
||||
runtimeConfig.DoNotPersistConfig = true
|
||||
}
|
||||
|
||||
if runtimeConfig.DisableAuth {
|
||||
os.Setenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED", "true")
|
||||
if len(os.Args) > 1 && (strings.Contains(os.Args[1], "com.8bit.bitwarden.json") || strings.Contains(os.Args[1], "chrome-extension://")) {
|
||||
browserbiometrics.Main(&runtimeConfig)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Execute(runtimeConfig)
|
||||
|
114
ui/autofill.py
114
ui/autofill.py
@ -1,114 +0,0 @@
|
||||
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
|
||||
import goldwarden
|
||||
import clipboard
|
||||
from threading import Thread
|
||||
import sys
|
||||
import os
|
||||
Notify.init("Goldwarden")
|
||||
|
||||
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()
|
||||
logins = goldwarden.get_vault_logins()
|
||||
if logins == None:
|
||||
os._exit(0)
|
||||
return
|
||||
app.autofill_window.logins = logins
|
||||
|
||||
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)
|
||||
|
||||
self.box = Gtk.Box()
|
||||
self.box.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.stack.add_named(self.box, "box")
|
||||
|
||||
self.text_view = Adw.EntryRow()
|
||||
self.text_view.set_title("Search")
|
||||
# on type func
|
||||
def on_type(entry):
|
||||
if len(entry.get_text()) > 1:
|
||||
self.history_list.show()
|
||||
else:
|
||||
self.history_list.hide()
|
||||
|
||||
while self.history_list.get_first_child() != None:
|
||||
self.history_list.remove(self.history_list.get_first_child())
|
||||
|
||||
self.filtered_logins = list(filter(lambda i: entry.get_text().lower() in i["name"].lower(), self.logins))
|
||||
if len( self.filtered_logins) > 10:
|
||||
self.filtered_logins = self.filtered_logins[0:10]
|
||||
self.starts_with_logins = list(filter(lambda i: i["name"].lower().startswith(entry.get_text().lower()), self.logins))
|
||||
self.other_logins = list(filter(lambda i: i not in self.starts_with_logins , self.filtered_logins))
|
||||
self.filtered_logins = None
|
||||
|
||||
for i in self.starts_with_logins + self.other_logins :
|
||||
action_row = Adw.ActionRow()
|
||||
action_row.set_title(i["name"])
|
||||
action_row.set_subtitle(i["username"])
|
||||
action_row.set_icon_name("dialog-password")
|
||||
action_row.set_activatable(True)
|
||||
action_row.password = i["password"]
|
||||
action_row.username = i["username"]
|
||||
self.history_list.append(action_row)
|
||||
self.starts_with_logins = None
|
||||
self.other_logins = None
|
||||
self.text_view.connect("changed", lambda entry: on_type(entry))
|
||||
self.box.append(self.text_view)
|
||||
|
||||
self.history_list = Gtk.ListBox()
|
||||
# margin'
|
||||
self.history_list.set_margin_start(10)
|
||||
self.history_list.set_margin_end(10)
|
||||
self.history_list.set_margin_top(10)
|
||||
self.history_list.set_margin_bottom(10)
|
||||
self.history_list.hide()
|
||||
|
||||
keycont = Gtk.EventControllerKey()
|
||||
def handle_keypress(cotroller, keyval, keycode, state, user_data):
|
||||
if keycode == 36:
|
||||
print("enter")
|
||||
self.hide()
|
||||
def do_autotype(username, password):
|
||||
time.sleep(0.5)
|
||||
goldwarden.autotype(username, password)
|
||||
os._exit(0)
|
||||
autotypeThread = Thread(target=do_autotype, args=(self.history_list.get_selected_row().username, self.history_list.get_selected_row().password,))
|
||||
autotypeThread.start()
|
||||
print(self.history_list.get_selected_row().get_title())
|
||||
if keyval == 112:
|
||||
print("copy password")
|
||||
clipboard.write(self.history_list.get_selected_row().password)
|
||||
Notify.Notification.new("Goldwarden", "Password Copied", "dialog-information").show()
|
||||
elif keyval == 117:
|
||||
print("copy username")
|
||||
clipboard.write(self.history_list.get_selected_row().username)
|
||||
notification=Notify.Notification.new("Goldwarden", "Username Copied", "dialog-information")
|
||||
notification.set_timeout(5)
|
||||
notification.show()
|
||||
|
||||
keycont.connect('key-pressed', handle_keypress, self)
|
||||
self.add_controller(keycont)
|
||||
|
||||
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")
|
||||
|
||||
app = MyApp(application_id="com.quexten.Goldwarden.autofill-menu")
|
||||
app.run(sys.argv)
|
@ -1,9 +0,0 @@
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
def write(text):
|
||||
# set path
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = env["PATH"] + ":/app/bin"
|
||||
process = subprocess.Popen(["/bin/sh", "-c", "wl-copy"], stdin=subprocess.PIPE, env=env)
|
||||
process.communicate(text.encode('utf-8'))
|
159
ui/goldwarden.py
159
ui/goldwarden.py
@ -1,159 +0,0 @@
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
|
||||
# if flatpak
|
||||
if os.path.exists("/app/bin/goldwarden"):
|
||||
BINARY_PATH = "/app/bin/goldwarden"
|
||||
else:
|
||||
res = subprocess.run(["which", "goldwarden"])
|
||||
BINARY_PATH = res.stdout.decode("utf-8").strip()
|
||||
|
||||
def set_api_url(url):
|
||||
restic_cmd = f"{BINARY_PATH} config set-api-url {url}"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
|
||||
def set_identity_url(url):
|
||||
restic_cmd = f"{BINARY_PATH} config set-identity-url {url}"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
|
||||
def set_notification_url(url):
|
||||
restic_cmd = f"{BINARY_PATH} config set-notifications-url {url}"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
|
||||
def set_client_id(client_id):
|
||||
restic_cmd = f"{BINARY_PATH} config set-client-id \"{client_id}\""
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed err", result.stderr)
|
||||
|
||||
def set_client_secret(client_secret):
|
||||
restic_cmd = f"{BINARY_PATH} config set-client-secret \"{client_secret}\""
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed err", result.stderr)
|
||||
|
||||
def login_with_password(email, password):
|
||||
restic_cmd = f"{BINARY_PATH} vault login --email {email}"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
if len(result.stderr.strip()) > 0:
|
||||
print(result.stderr)
|
||||
if "password" in result.stderr:
|
||||
return "badpass"
|
||||
else:
|
||||
if "Logged in" in result.stderr:
|
||||
print("ok")
|
||||
return "ok"
|
||||
return "error"
|
||||
print("ok")
|
||||
return "ok"
|
||||
|
||||
def login_passwordless(email):
|
||||
restic_cmd = f"{BINARY_PATH} vault login --email {email} --passwordless"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
|
||||
def is_pin_enabled():
|
||||
restic_cmd = f"{BINARY_PATH} vault pin status"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
# check if contains enabled
|
||||
return "enabled" in result.stderr
|
||||
|
||||
def enable_pin():
|
||||
restic_cmd = f"{BINARY_PATH} vault pin set"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
|
||||
def unlock():
|
||||
restic_cmd = f"{BINARY_PATH} vault unlock"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
|
||||
def lock():
|
||||
restic_cmd = f"{BINARY_PATH} vault lock"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
|
||||
def purge():
|
||||
restic_cmd = f"{BINARY_PATH} vault purge"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
|
||||
def get_vault_status():
|
||||
restic_cmd = f"{BINARY_PATH} vault status"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
try:
|
||||
return json.loads(result.stdout)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def get_vault_logins():
|
||||
restic_cmd = f"{BINARY_PATH} logins list"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
try:
|
||||
return json.loads(result.stdout)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def get_runtime_config():
|
||||
restic_cmd = f"{BINARY_PATH} config get-runtime-config"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
try:
|
||||
return json.loads(result.stdout)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def autotype(username, password):
|
||||
# environment
|
||||
env = os.environ.copy()
|
||||
env["PASSWORD"] = password
|
||||
restic_cmd = f"{BINARY_PATH} autotype --username {username}"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True, env=env)
|
||||
print(result.stderr)
|
||||
print(result.stdout)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||
|
||||
def is_daemon_running():
|
||||
restic_cmd = f"{BINARY_PATH} vault status"
|
||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
return False
|
||||
daemon_not_running = ("daemon running?" in result.stderr or "daemon running" in result.stderr)
|
||||
return not daemon_not_running
|
||||
|
||||
def run_daemon():
|
||||
restic_cmd = f"{BINARY_PATH} daemonize"
|
||||
# print while running
|
||||
result = subprocess.Popen(restic_cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
if result.returncode != 0:
|
||||
print("Failed err", result.stderr)
|
||||
for line in result.stdout:
|
||||
print(line.decode("utf-8"))
|
||||
result.wait()
|
||||
print("quitting goldwarden daemon")
|
||||
return result.returncode
|
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)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user