Compare commits

...

108 Commits

Author SHA1 Message Date
github-actions[bot]
f77b7ad307
2024-02-17 13:30:06 +00:00
Bernd Schoolmann
715d04c5ab
Update release.yml 2024-02-17 14:29:03 +01:00
github-actions[bot]
2da6bcc138
2024-02-17 13:26:01 +00:00
Bernd Schoolmann
93dc64ae36
Update PKGBUILD to 0.2.12 2024-02-17 14:24:58 +01:00
github-actions[bot]
0ea0bcee94
2024-02-17 13:19:33 +00:00
Bernd Schoolmann
8b95af6458
Fix broken arch ci due to maintainer account renaming 2024-02-17 14:17:27 +01:00
Bernd Schoolmann
969ee1ff49
Update com.quexten.Goldwarden.metainfo.xml 2024-02-17 14:03:09 +01:00
github-actions[bot]
b3f701a416
2024-02-17 13:00:35 +00:00
Bernd Schoolmann
f99d618b33
Fix flatpak build 2024-02-17 13:44:00 +01:00
Bernd Schoolmann
f894af5fb3
Update PKGBUILD 2024-02-17 13:39:47 +01:00
Bernd Schoolmann
29bd40384b
Fix autotype and remove debug logging 2024-02-17 13:38:54 +01:00
Bernd Schoolmann
6ee67d6569
Use gtk clipboard instead of wl clipboard 2024-02-17 13:32:58 +01:00
Bernd Schoolmann
13bd29dbf9
Add browser biometrics setup window 2024-02-17 12:48:26 +01:00
Bernd Schoolmann
623fdcb719
Add flatpak biometric setup 2024-02-17 12:28:04 +01:00
Bernd Schoolmann
3b2d1fcf11
Improve biometric socket path detection 2024-02-17 12:11:30 +01:00
Bernd Schoolmann
5cc502580f
Increase read buffer to 4 MiB 2024-02-17 11:30:16 +01:00
Bernd Schoolmann
f7da373fa0
Fix crash in ui 2024-02-17 11:22:49 +01:00
Bernd Schoolmann
c15447aab3
Add ssh key and keyboard shortcut windows 2024-02-17 11:00:33 +01:00
Bernd Schoolmann
9ac1d8100a
Grant other sessions while pin session is active 2024-02-17 09:52:19 +01:00
Bernd Schoolmann
606dd176ef
Improve quickaccess keyboard navigation 2024-02-17 09:51:41 +01:00
Bernd Schoolmann
592ff48148
Fix autostart in flatpak 2024-02-17 07:20:43 +01:00
Bernd Schoolmann
f6c501f0d2
Merge pull request #117 from Mte90/patch-2
Silent notice on quickaccess.py
2024-02-12 18:50:38 +01:00
Bernd Schoolmann
87e13098a0
Merge pull request #116 from Mte90/patch-1
Create __init__.py
2024-02-12 18:50:17 +01:00
Daniele Scasciafratte
7e4d5a4b76
Silent notice on quickaccess.py 2024-02-12 18:46:16 +01:00
Daniele Scasciafratte
873c4fb799
Create __init__.py
Avoid this crash if the python script is executed:
```
Traceback (most recent call last):
  File "/home/mte90/Desktop/kde/goldwarden/gui/goldwarden_ui_main.py", line 2, in <module>
    import src.linux.main as linux_main
ModuleNotFoundError: No module named 'src.linux'
```
2024-02-12 18:34:18 +01:00
Bernd Schoolmann
11ad8c4f48
Attempt to fix gui login errors when uri is missing 2024-02-12 18:06:53 +01:00
Bernd Schoolmann
a94d8f052b
Reduce number of ssh approval prompts 2024-02-12 17:41:23 +01:00
Bernd Schoolmann
9c840f3edc
Attempt to fix settings freeze 2024-02-12 05:15:43 +01:00
Bernd Schoolmann
761fb91a8a
Disable prctl dumpable protection under delve debugging 2024-02-12 03:32:44 +01:00
Bernd Schoolmann
cf9f3f33dd
Fix login errors in ui 2024-02-12 03:31:24 +01:00
Bernd Schoolmann
20e71663e3
Merge pull request #114 from quexten/feature/pin-cache
Improved keyboard navigation on ui
2024-02-10 17:21:55 +01:00
Bernd Schoolmann
86848065d1
Fix ui row selection on keyboard navigation 2024-02-10 16:35:58 +01:00
Bernd Schoolmann
76baf9eca0
Improve quick-access keyboard navigation 2024-02-09 22:52:21 +01:00
Bernd Schoolmann
aeb9888f7f
Fix incorrect binary path 2024-02-09 22:51:37 +01:00
Bernd Schoolmann
c1f21768a1
Merge pull request #113 from quexten/feature/pin-cache
Implement pincache
2024-02-09 21:05:15 +01:00
Bernd Schoolmann
01f36cc571
Implement pincache 2024-02-09 20:48:44 +01:00
Bernd Schoolmann
dc05cdfee6
Lock down session commands 2024-02-09 19:46:29 +01:00
Bernd Schoolmann
69797aa50b
Remove old config options 2024-02-09 19:37:09 +01:00
Bernd Schoolmann
35cc409943
Lock down session auth mode 2024-02-09 19:32:01 +01:00
Bernd Schoolmann
f1395d4b6d
Re-apply fix for binary detection in gui 2024-02-09 19:00:20 +01:00
Bernd Schoolmann
83b808f44c
Remove unused file 2024-02-09 18:41:27 +01:00
Bernd Schoolmann
472c9f59da
Fix old autofill dbus api 2024-02-09 18:37:22 +01:00
Bernd Schoolmann
f224276749
Prevent setup as root 2024-02-09 18:05:15 +01:00
Bernd Schoolmann
52156f8892
Merge pull request #100 from quexten/feature/authenticated-connection
Authenticated Session & External Pinentry
2024-02-09 17:36:52 +01:00
Bernd Schoolmann
2e42a798e3
Remove commented out code 2024-02-09 17:30:19 +01:00
Bernd Schoolmann
8d257c265e
Merge branch 'main' into feature/authenticated-connection 2024-02-09 17:28:56 +01:00
Bernd Schoolmann
a6d3a1026d
Fix pinentry in flatpak 2024-02-09 17:26:41 +01:00
Bernd Schoolmann
563ad6fa45
Remove log that breaks authentication 2024-02-09 16:56:25 +01:00
Bernd Schoolmann
4a3e8ca819
Merge pull request #110 from Mte90/patch-3
PyGIWarning: Gtk was imported without specifying a version first
2024-02-09 16:15:14 +01:00
Bernd Schoolmann
8a41543601
Merge pull request #109 from Mte90/patch-1
better goldwarden command path detection
2024-02-09 16:13:20 +01:00
Bernd Schoolmann
ed6060eae4
Update ui dbus interface and make background activate work again 2024-02-09 16:05:49 +01:00
Daniele Scasciafratte
417567f549
PyGIWarning: Gtk was imported without specifying a version first 2024-02-09 16:01:13 +01:00
Daniele Scasciafratte
200bce7d7e
better goldwarden command path detection
in this way works always
2024-02-09 15:57:58 +01:00
Bernd Schoolmann
53989ebaa3
Rename gui dirs and remove non-required dependencies 2024-02-09 15:47:20 +01:00
Bernd Schoolmann
39d1ec6980
Re-enable daemon running in the background in flatpak 2024-02-09 15:04:32 +01:00
Bernd Schoolmann
75982ad322
Enable regular pinentry 2024-02-09 15:03:42 +01:00
Bernd Schoolmann
219a8cb849
Fix main python filepaths in flatpak 2024-02-09 14:20:38 +01:00
Bernd Schoolmann
14618b3d07
Fix main python file path in flatpak 2024-02-09 14:11:05 +01:00
Bernd Schoolmann
ef276a0019
Add logging to daemon 2024-02-09 14:04:03 +01:00
Bernd Schoolmann
5805931c00
Fix flatpak 2024-02-09 14:03:46 +01:00
Bernd Schoolmann
069afeae27
Implement pinentry integration for ui 2024-02-09 14:03:27 +01:00
Bernd Schoolmann
ac0e84a46f
Implement initial pinentry 2024-02-09 00:24:28 +01:00
Bernd Schoolmann
8b08d5841a
Disable extra prompt for list logins 2024-02-08 18:18:25 +01:00
Bernd Schoolmann
fe64cc7807
More fixes to make autotype work 2024-02-08 18:17:51 +01:00
Bernd Schoolmann
ca17380758
FIx freeze and remove some logging 2024-02-08 17:12:13 +01:00
Bernd Schoolmann
cf6221c080
Add initial authenticated connection work 2024-02-08 16:35:07 +01:00
Bernd Schoolmann
f3196863bb
Add sync return url 2024-02-04 19:56:36 +01:00
Bernd Schoolmann
d0e0d66509
Add experimental send creation support 2024-02-04 19:34:09 +01:00
Bernd Schoolmann
b297bf1ea3
Fix arm64 windows release 2024-02-04 10:21:58 +01:00
Bernd Schoolmann
cc0c9d40aa
Attempt to fix windows aarch64 build 2024-02-04 10:19:39 +01:00
Bernd Schoolmann
b58d256744
Update release.yml 2024-02-04 10:12:11 +01:00
Bernd Schoolmann
db64c3771e
Add windows arm64 build 2024-02-04 10:10:26 +01:00
Bernd Schoolmann
81efe5f7e5
Update release.yml 2024-02-04 10:08:51 +01:00
Bernd Schoolmann
2f200a2794
Enable fido2 on windows ci builds 2024-02-04 10:07:03 +01:00
Bernd Schoolmann
249bbedc13
Add Fido2 to windows ci 2024-02-04 10:05:13 +01:00
Bernd Schoolmann
8a3cb53e77
Add scoop to windows ci 2024-02-04 10:02:24 +01:00
Bernd Schoolmann
490c4982c3
Add fido2 on mac CI 2024-02-04 10:00:32 +01:00
Bernd Schoolmann
2a96b74110
Update go-libfido2 2024-02-04 09:57:24 +01:00
Bernd Schoolmann
c8e964ebae
Update go.yml 2024-02-04 09:51:53 +01:00
Bernd Schoolmann
d655083a8c
Add fido2 macos build 2024-02-04 09:45:34 +01:00
Bernd Schoolmann
64f1174474
Add homebrew to macos ci 2024-02-04 09:39:39 +01:00
Bernd Schoolmann
f135e8b302
Cleanup sockets 2024-02-04 01:58:38 +01:00
Bernd Schoolmann
4afd6ef9b1
Make ssh sockets close on exit 2024-02-04 01:58:37 +01:00
Bernd Schoolmann
f5c20905a9
Update Readme.md 2024-02-04 01:21:12 +01:00
Bernd Schoolmann
6f10836300
Make passwordless auth only act on notification click 2024-02-04 01:15:26 +01:00
Bernd Schoolmann
01fe7b7ff4
Fix windows build 2024-02-04 00:56:55 +01:00
Bernd Schoolmann
39c175a1f6
Add notifications on macos 2024-02-04 00:53:16 +01:00
Bernd Schoolmann
1933917634
Add notifications on windows 2024-02-04 00:49:42 +01:00
Bernd Schoolmann
7949ed63f3
Attempt to fix osx build 2024-02-04 00:36:08 +01:00
Bernd Schoolmann
ab9aaf2fe1
Attempt to fix osx build 2024-02-04 00:34:09 +01:00
Bernd Schoolmann
f79dba0e54
Fix windows go-winio dependency 2024-02-04 00:28:30 +01:00
Bernd Schoolmann
599cc1003f
Fix ssh daemon on windows 2024-02-04 00:24:40 +01:00
Bernd Schoolmann
1e24bce546
Add windows ssh named pipe 2024-02-04 00:08:25 +01:00
Bernd Schoolmann
1455cf9cb2
Fix config dir creation on windows 2024-02-03 23:15:29 +01:00
Bernd Schoolmann
e0cb8a9187
Vendor out the keybase pinentry 2024-02-03 22:55:49 +01:00
Bernd Schoolmann
f60fcfd408
Add pinentry on windows & macos 2024-02-03 22:18:11 +01:00
Bernd Schoolmann
f3a33eb36c
Add Apple silicone builds to release CI 2024-02-03 20:28:07 +01:00
Bernd Schoolmann
4add545000
Update go.yml 2024-02-03 20:23:56 +01:00
Bernd Schoolmann
7ca312ad53
Add Apple silicone build 2024-02-03 20:22:09 +01:00
Bernd Schoolmann
01ecb54d82
Fix autotype on kde 2024-01-23 18:09:18 +01:00
Bernd Schoolmann
def7f7a6ba
Add totp and launch url shortcuts to settings ui 2024-01-20 17:35:58 +01:00
Bernd Schoolmann
25b72e4c43
Implement totp copy and url launch 2024-01-20 17:34:42 +01:00
Bernd Schoolmann
ce17426063
Change order in login menu 2024-01-20 13:27:32 +01:00
Bernd Schoolmann
8a6e4c910d
Fix typo 2024-01-20 13:07:59 +01:00
Bernd Schoolmann
5ba0497788
Move settings and add web vault launch entry 2024-01-20 13:06:41 +01:00
Bernd Schoolmann
2f21f27da9
Add item uuid launch to quick access menu 2024-01-20 13:04:04 +01:00
Bernd Schoolmann
8eb55f2808
Add url auto-config and web vault config 2024-01-20 12:32:27 +01:00
Bernd Schoolmann
b166ca8d61
Update Readme.md 2024-01-20 06:23:32 +01:00
102 changed files with 3346 additions and 1131 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

@ -103,7 +103,7 @@ func ensureIsNotLocked(action Action) Action {
}
}
systemauth.CreatePinSession(*ctx)
systemauth.CreatePinSession(*ctx, systemauth.SSHTTL)
}
return action(request, cfg, vault, ctx)

View File

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

View File

@ -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!",

View File

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

View File

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

View File

@ -10,7 +10,6 @@ import (
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"time"
)
@ -114,7 +113,7 @@ func makeAuthenticatedHTTPRequest(ctx context.Context, req *http.Request, recv i
return &errStatusCode{res.StatusCode, body}
}
if err := json.Unmarshal(body, recv); err != nil {
fmt.Fprintln(os.Stderr, string(body))
fmt.Println(string(body))
return err
}
return nil

111
agent/bitwarden/send.go Normal file
View 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
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
//go:build linux || freebsd
//go:build freebsd || linux
package notify

24
agent/notify/windows.go Normal file
View 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
}

View 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
}

View File

@ -0,0 +1,6 @@
//go:build delve
// +build delve
package isdelve
const Enabled = true

View File

@ -0,0 +1,6 @@
//go:build !delve
// +build !delve
package isdelve
const Enabled = false

View File

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

View File

@ -17,6 +17,7 @@ type CallingContext struct {
ParentProcessPid int
GrandParentProcessPid int
Error bool
Authenticated bool
}
func GetCallingContext(connection net.Conn) CallingContext {
@ -30,6 +31,7 @@ func GetCallingContext(connection net.Conn) CallingContext {
ParentProcessPid: 0,
GrandParentProcessPid: 0,
Error: true,
Authenticated: false,
}
if err != nil {
return errorContext

View File

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

View 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)
}
}

View 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)
}
}

View File

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

View File

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

View File

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

View File

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

View 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.

View 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
}

View 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
}

View File

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

View 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
}

View 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"
}

View 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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package cmd
import (
"encoding/json"
"fmt"
"strings"
@ -30,12 +31,12 @@ var setApiUrlCmd = &cobra.Command{
switch result.(type) {
case messages.ActionResponse:
if result.(messages.ActionResponse).Success {
println("Done")
fmt.Println("Done")
} else {
println("Setting api url failed: " + result.(messages.ActionResponse).Message)
fmt.Println("Setting api url failed: " + result.(messages.ActionResponse).Message)
}
default:
println("Wrong IPC response type")
fmt.Println("Wrong IPC response type")
}
},
@ -63,12 +64,12 @@ var setIdentityURLCmd = &cobra.Command{
switch result.(type) {
case messages.ActionResponse:
if result.(messages.ActionResponse).Success {
println("Done")
fmt.Println("Done")
} else {
println("Setting identity url failed: " + result.(messages.ActionResponse).Message)
fmt.Println("Setting identity url failed: " + result.(messages.ActionResponse).Message)
}
default:
println("Wrong IPC response type")
fmt.Println("Wrong IPC response type")
}
},
@ -96,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)

View File

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

View File

@ -4,6 +4,8 @@ Copyright © 2023 NAME HERE <EMAIL ADDRESS>
package cmd
import (
"fmt"
"github.com/quexten/goldwarden/ipc/messages"
"github.com/spf13/cobra"
)
@ -17,7 +19,7 @@ var loginCmd = &cobra.Command{
request := messages.DoLoginRequest{}
email, _ := cmd.Flags().GetString("email")
if email == "" {
println("Error: No email specified")
fmt.Println("Error: No email specified")
return
}
@ -34,12 +36,12 @@ var loginCmd = &cobra.Command{
switch result.(type) {
case messages.ActionResponse:
if result.(messages.ActionResponse).Success {
println("Logged in")
fmt.Println("Logged in")
} else {
println("Login failed: " + result.(messages.ActionResponse).Message)
fmt.Println("Login failed: " + result.(messages.ActionResponse).Message)
}
default:
println("Wrong IPC response type for login")
fmt.Println("Wrong IPC response type for login")
}
},
}

View File

@ -53,7 +53,7 @@ var getLoginCmd = &cobra.Command{
}
break
case messages.ActionResponse:
println("Error: " + resp.(messages.ActionResponse).Message)
fmt.Println("Error: " + resp.(messages.ActionResponse).Message)
return
}
},
@ -72,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

View File

@ -1,6 +1,8 @@
package cmd
import (
"fmt"
"github.com/quexten/goldwarden/ipc/messages"
"github.com/spf13/cobra"
)
@ -25,12 +27,12 @@ var setPinCmd = &cobra.Command{
switch result.(type) {
case messages.ActionResponse:
if result.(messages.ActionResponse).Success {
println("Pin updated")
fmt.Println("Pin updated")
} else {
println("Pin updating failed: " + result.(messages.ActionResponse).Message)
fmt.Println("Pin updating failed: " + result.(messages.ActionResponse).Message)
}
default:
println("Wrong response type")
fmt.Println("Wrong response type")
}
},
}
@ -48,9 +50,9 @@ var pinStatusCmd = &cobra.Command{
switch result.(type) {
case messages.ActionResponse:
println("Pin status: " + result.(messages.ActionResponse).Message)
fmt.Println("Pin status: " + result.(messages.ActionResponse).Message)
default:
println("Wrong response type")
fmt.Println("Wrong response type")
}
},
}

View File

@ -1,16 +1,16 @@
package cmd
import (
"fmt"
"os"
"github.com/quexten/goldwarden/agent"
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/client"
"github.com/quexten/goldwarden/ipc/messages"
"github.com/spf13/cobra"
)
var commandClient client.Client
var commandClient client.UnixSocketClient
var runtimeConfig config.RuntimeConfig
var rootCmd = &cobra.Command{
@ -24,13 +24,7 @@ var rootCmd = &cobra.Command{
func Execute(cfg config.RuntimeConfig) {
runtimeConfig = cfg
goldwardenSingleProcess := os.Getenv("GOLDWARDEN_SINGLE_PROCESS")
if goldwardenSingleProcess == "true" {
recv, send := agent.StartVirtualAgent(runtimeConfig)
commandClient = client.NewVirtualClient(send, recv)
} else {
commandClient = client.NewUnixSocketClient(&cfg)
}
commandClient = client.NewUnixSocketClient(&cfg)
err := rootCmd.Execute()
if err != nil {
@ -62,8 +56,8 @@ func loginIfRequired() error {
func handleSendToAgentError(err error) {
if err != nil {
println("Error: " + err.Error())
println("Is the daemon running?")
fmt.Println("Error: " + err.Error())
fmt.Println("Is the daemon running?")
return
}
}

View File

@ -4,6 +4,7 @@ Copyright © 2023 NAME HERE <EMAIL ADDRESS>
package cmd
import (
"fmt"
"os"
"os/exec"
@ -19,7 +20,7 @@ var runCmd = &cobra.Command{
The variables are stored as a secure note. Consult the documentation for more information.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
println("Error: No command specified")
fmt.Println("Error: No command specified")
return
}
@ -43,7 +44,7 @@ var runCmd = &cobra.Command{
env = append(env, key+"="+value)
}
case messages.ActionResponse:
println("Error: " + result.(messages.ActionResponse).Message)
fmt.Println("Error: " + result.(messages.ActionResponse).Message)
return
}

53
cmd/send.go Normal file
View 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
View File

@ -0,0 +1,90 @@
package cmd
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/quexten/goldwarden/ipc/messages"
"github.com/spf13/cobra"
)
// sessionCmd represents the run command
var sessionCmd = &cobra.Command{
Use: "session",
Hidden: true,
Short: "Starts a new session",
Long: `Starts a new session.`,
Run: func(cmd *cobra.Command, args []string) {
for {
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
text = strings.TrimSuffix(text, "\n")
args := strings.Split(text, " ")
rootCmd.SetArgs(args)
rootCmd.Execute()
}
},
}
var pinentry = &cobra.Command{
Use: "pinentry",
Hidden: true,
Short: "Registers as a pinentry program",
Long: `Registers as a pinentry program.`,
Run: func(cmd *cobra.Command, args []string) {
conn, err := commandClient.Connect()
if err != nil {
panic(err)
}
defer conn.Close()
_, err = conn.SendCommand(messages.PinentryRegistrationRequest{})
if err != nil {
panic(err)
}
for {
response := conn.ReadMessage()
switch response.(type) {
case messages.PinentryPinRequest:
fmt.Println("pin-request" + "," + response.(messages.PinentryPinRequest).Message)
case messages.PinentryApprovalRequest:
fmt.Println("approval-request" + "," + response.(messages.PinentryApprovalRequest).Message)
}
// read line
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
text = strings.TrimSuffix(text, "\n")
switch response.(type) {
case messages.PinentryPinRequest:
err = conn.WriteMessage(messages.PinentryPinResponse{Pin: text})
case messages.PinentryApprovalRequest:
err = conn.WriteMessage(messages.PinentryApprovalResponse{Approved: text == "true"})
}
}
},
}
var authenticateSession = &cobra.Command{
Use: "authenticate-session",
Hidden: true,
Short: "Authenticates a session",
Long: `Authenticates a session.`,
Run: func(cmd *cobra.Command, args []string) {
token := args[0]
response, err := commandClient.SendToAgent(messages.SessionAuthRequest{Token: token})
if err != nil {
panic(err)
}
fmt.Println(response.(messages.SessionAuthResponse).Verified)
},
}
func init() {
rootCmd.AddCommand(sessionCmd)
rootCmd.AddCommand(pinentry)
rootCmd.AddCommand(authenticateSession)
}

View File

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

View File

@ -47,7 +47,7 @@ var sshAddCmd = &cobra.Command{
}
break
case messages.ActionResponse:
println("Error: " + result.(messages.ActionResponse).Message)
fmt.Println("Error: " + result.(messages.ActionResponse).Message)
return
}
},
@ -74,7 +74,7 @@ var listSSHCmd = &cobra.Command{
}
break
case messages.ActionResponse:
println("Error: " + result.(messages.ActionResponse).Message)
fmt.Println("Error: " + result.(messages.ActionResponse).Message)
return
}
},

View File

@ -1,6 +1,7 @@
package cmd
import (
"encoding/json"
"fmt"
"time"
@ -30,12 +31,12 @@ var unlockCmd = &cobra.Command{
switch result.(type) {
case messages.ActionResponse:
if result.(messages.ActionResponse).Success {
println("Unlocked")
fmt.Println("Unlocked")
} else {
println("Not unlocked: " + result.(messages.ActionResponse).Message)
fmt.Println("Not unlocked: " + result.(messages.ActionResponse).Message)
}
default:
println("Wrong response type")
fmt.Println("Wrong response type")
}
},
}
@ -56,12 +57,12 @@ var lockCmd = &cobra.Command{
switch result.(type) {
case messages.ActionResponse:
if result.(messages.ActionResponse).Success {
println("Locked")
fmt.Println("Locked")
} else {
println("Not locked: " + result.(messages.ActionResponse).Message)
fmt.Println("Not locked: " + result.(messages.ActionResponse).Message)
}
default:
println("Wrong response type")
fmt.Println("Wrong response type")
}
},
}
@ -82,12 +83,12 @@ var purgeCmd = &cobra.Command{
switch result.(type) {
case messages.ActionResponse:
if result.(messages.ActionResponse).Success {
println("Purged")
fmt.Println("Purged")
} else {
println("Not purged: " + result.(messages.ActionResponse).Message)
fmt.Println("Not purged: " + result.(messages.ActionResponse).Message)
}
default:
println("Wrong response type")
fmt.Println("Wrong response type")
}
},
}
@ -107,18 +108,19 @@ var statusCmd = &cobra.Command{
switch result.(type) {
case messages.VaultStatusResponse:
response := map[string]interface{}{}
status := result.(messages.VaultStatusResponse)
fmt.Println("{")
fmt.Println(" \"locked\":", status.Locked, ",")
fmt.Println(" \"loginEntries\":", status.NumberOfLogins, ",")
fmt.Println(" \"noteEntries\":", status.NumberOfNotes, ",")
fmt.Println(" \"lastSynced\": \"" + time.Unix(status.LastSynced, 0).String() + "\",")
fmt.Println(" \"websocketConnected\":", status.WebsockedConnected, ",")
fmt.Println(" \"pinSet\":", status.PinSet, ",")
fmt.Println(" \"loggedIn\":", status.LoggedIn)
fmt.Println("}")
response["locked"] = status.Locked
response["loginEntries"] = status.NumberOfLogins
response["noteEntries"] = status.NumberOfNotes
response["lastSynced"] = time.Unix(status.LastSynced, 0).String()
response["websocketConnected"] = status.WebsockedConnected
response["pinSet"] = status.PinSet
response["loggedIn"] = status.LoggedIn
responseJSON, _ := json.Marshal(response)
fmt.Println(string(responseJSON))
default:
println("Wrong response type")
fmt.Println("Wrong response type")
}
},
}

View File

@ -2,7 +2,7 @@ id: com.quexten.Goldwarden
runtime: org.gnome.Platform
runtime-version: '45'
sdk: org.gnome.Sdk
command: main.py
command: goldwarden_ui_main.py
finish-args:
# Allow network access for sync
- --share=network
@ -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
View File

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

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

@ -0,0 +1 @@

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

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

View File

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

1
gui/requirements.txt Normal file
View File

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

View 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
View File

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

View File

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

178
gui/src/gui/quickaccess.py Normal file
View 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)

View File

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

View File

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

View File

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

View File

@ -0,0 +1,41 @@
from gi.repository import Gtk
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
from threading import Thread
import subprocess
import os
root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, os.pardir))
daemon_token = None
class GoldwardenDBUSService(dbus.service.Object):
def __init__(self):
bus_name = dbus.service.BusName('com.quexten.Goldwarden.gui', bus=dbus.SessionBus())
dbus.service.Object.__init__(self, bus_name, '/com/quexten/Goldwarden/gui')
@dbus.service.method('com.quexten.Goldwarden.gui.actions')
def quickaccess(self):
p = subprocess.Popen(["python3", "-m", "src.gui.quickaccess"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{daemon_token}\n".encode())
p.stdin.flush()
return ""
@dbus.service.method('com.quexten.Goldwarden.gui.actions')
def settings(self):
p = subprocess.Popen(["python3", "-m", "src.gui.settings"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{daemon_token}\n".encode())
p.stdin.flush()
return ""
def daemon():
DBusGMainLoop(set_as_default=True)
service = GoldwardenDBUSService()
from gi.repository import GLib, GObject as gobject
gobject.MainLoop().run()
def run_daemon(token):
global daemon_token
daemon_token = token
thread = Thread(target=daemon)
thread.start()

View File

@ -0,0 +1,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()

View File

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

20
gui/src/services/totp.py Normal file
View 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)

View File

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

View File

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

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

60
main.go
View File

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

View File

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

View File

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

View File

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

View File

@ -1,53 +0,0 @@
#!/usr/bin/python
import time
import subprocess
from tendo import singleton
import monitors.dbus_autofill_monitor
import sys
import goldwarden
from threading import Thread
import os
isflatpak = os.path.exists("/.flatpak-info")
pathprefix = "/app/bin/" if isflatpak else "./"
try:
subprocess.Popen(["python3", f'{pathprefix}background.py'], start_new_session=True)
except:
pass
is_hidden = "--hidden" in sys.argv
if not is_hidden:
try:
subprocess.Popen(["python3", f'{pathprefix}settings.py'], start_new_session=True)
except:
subprocess.Popen(["python3", f'{pathprefix}settings.py'], start_new_session=True)
pass
try:
me = singleton.SingleInstance()
except:
exit()
def run_daemon():
# todo: do a proper check
if is_hidden:
time.sleep(20)
print("IS daemon running", goldwarden.is_daemon_running())
if not goldwarden.is_daemon_running():
print("running daemon")
goldwarden.run_daemon()
print("daemon running")
thread = Thread(target=run_daemon)
thread.start()
def on_autofill():
subprocess.Popen(["python3", f'{pathprefix}autofill.py'], start_new_session=True)
monitors.dbus_autofill_monitor.on_autofill = lambda: on_autofill()
monitors.dbus_autofill_monitor.run_daemon()
while True:
time.sleep(60)

Some files were not shown because too many files have changed in this diff Show More