Skip to content

Commit

Permalink
Merge pull request #18 from quexten/feature/gtk-ui
Browse files Browse the repository at this point in the history
GTK Based Flatpak
  • Loading branch information
quexten authored Dec 23, 2023
2 parents 264bc4c + 16968de commit 1a91189
Show file tree
Hide file tree
Showing 60 changed files with 1,568 additions and 1,634 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.vscode
*debug*
goldwarden*
goldwarden
__pycache__
.flatpak-builder
19 changes: 10 additions & 9 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ On Linux, you need at least a working Polkit installation and a pinentry agent.

### Installation

#### Flatpak (WIP)
There is a flatpak that includes a small UI, autotype functionality and autostarting of the daemon.
**Not yet on flathub**

#### CLI
On Arch linux, or other distributions with access to the AUR, simply:
```
yay -S goldwarden
Expand Down Expand Up @@ -85,9 +90,9 @@ Run a command with injected environment variables
goldwarden run -- <command>
```

Autofill
Autofill (Flatpak Only)
```
goldwarden autofill --layout <keyboard-layout>
dbus-send --type=method_call --dest=com.quexten.goldwarden /com/quexten/goldwarden com.quexten.goldwarden.Autofill.autofill
```
(Create a hotkey for this depending on your desktop environment)

Expand Down Expand Up @@ -176,20 +181,16 @@ restic backup
[goldwarden_autofill.webm](https://github.com/quexten/goldwarden/assets/11866552/6ac7cdc2-0cd7-42fd-9fd0-cfff26e2ceee)

You can bind this to a hotkey in your desktop environment (i.e i3/sway config file, Gnome custom shortcuts, etc).
```
dbus-send --type=method_call --dest=com.quexten.goldwarden /com/quexten/goldwarden com.quexten.goldwarden.Autofill.autofill
```

#### XDG-RemoteDesktop-Portal

By default, the remote desktop portal is used. As long as your desktop environment handle this (KDE and Gnome do, wlroots does not yet)
this enables autotyping without having to modify permissions.
`goldwarden autofill`

#### (Legacy) Uinput
If your desktop environment does not implement the remotedesktop portal, your only other option is uinput based autotype. This requires your user
to have access to the input group to use uinput to autotype. This needs a keyboardlayout to map the letters to
keycodes. Currently supported are qwerty and dvorak.
`goldwarden autofill --layout qwerty`
`goldwarden autofill --layout dvorak`

### Login with device
Approving other devices works out of the box and is enabled by default. If the agent is unlocked, you will be prompted
to approve the device. If you want to log into goldwarden using another device, add the "--passwordless" parameter to the login command.
Expand Down
11 changes: 10 additions & 1 deletion agent/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,16 @@ func sync(ctx context.Context, vault *vault.Vault, cfg *config.Config) bool {
if err != nil {
return false
}
protectedUserSymetricKey, err := crypto.SymmetricEncryptionKeyFromBytes(userSymmetricKey)

var protectedUserSymetricKey crypto.SymmetricEncryptionKey
if vault.Keyring.IsMemguard {
protectedUserSymetricKey, err = crypto.MemguardSymmetricEncryptionKeyFromBytes(userSymmetricKey)
} else {
protectedUserSymetricKey, err = crypto.MemorySymmetricEncryptionKeyFromBytes(userSymmetricKey)
}
if err != nil {
return false
}

err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, cfg, &protectedUserSymetricKey, true)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions agent/actions/browserbiometrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,33 @@ import (
)

func handleGetBiometricsKey(request messages.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (response messages.IPCMessage, err error) {
actionsLog.Info("Browser Biometrics: Key requested, verifying biometrics...")
if !(systemauth.VerifyPinSession(*ctx) || biometrics.CheckBiometrics(biometrics.BrowserBiometrics)) {
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
Success: false,
Message: "not approved",
})
actionsLog.Info("Browser Biometrics: Biometrics not approved %v", err)
if err != nil {
return messages.IPCMessage{}, err
}
return response, nil
}

actionsLog.Info("Browser Biometrics: Biometrics verified, asking for approval...")
if approved, err := pinentry.GetApproval("Approve Credential Access", fmt.Sprintf("%s on %s>%s>%s is trying to access your vault encryption key for browser biometric unlock.", ctx.UserName, ctx.GrandParentProcessName, ctx.ParentProcessName, ctx.ProcessName)); err != nil || !approved {
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
Success: false,
Message: "not approved",
})
actionsLog.Info("Browser Biometrics: Biometrics not approved %v", err)
if err != nil {
return messages.IPCMessage{}, err
}
return response, nil
}

actionsLog.Info("Browser Biometrics: Approved, getting key...")
masterKey, err := cfg.GetMasterKey()
if err != nil {
return messages.IPCMessage{}, err
Expand All @@ -45,6 +50,7 @@ func handleGetBiometricsKey(request messages.IPCMessage, cfg *config.Config, vau
response, err = messages.IPCMessageFromPayload(messages.GetBiometricsKeyResponse{
Key: masterKeyB64,
})
actionsLog.Info("Browser Biometrics: Sending key...")
return response, err
}

Expand Down
9 changes: 7 additions & 2 deletions agent/actions/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,15 @@ func handleLogin(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vault
return
}

cfg.SetUserSymmetricKey(vault.Keyring.AccountKey.Bytes())
cfg.SetUserSymmetricKey(vault.Keyring.GetAccountKey().Bytes())
cfg.SetMasterPasswordHash([]byte(masterpasswordHash))
cfg.SetMasterKey([]byte(masterKey.GetBytes()))
protectedUserSymetricKey, err := crypto.SymmetricEncryptionKeyFromBytes(vault.Keyring.AccountKey.Bytes())
var protectedUserSymetricKey crypto.SymmetricEncryptionKey
if vault.Keyring.IsMemguard {
protectedUserSymetricKey, err = crypto.MemguardSymmetricEncryptionKeyFromBytes(vault.Keyring.GetAccountKey().Bytes())
} else {
protectedUserSymetricKey, err = crypto.MemorySymmetricEncryptionKeyFromBytes(vault.Keyring.GetAccountKey().Bytes())
}
if err != nil {
var payload = messages.ActionResponse{
Success: false,
Expand Down
12 changes: 11 additions & 1 deletion agent/actions/logins.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ 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 to list credentials (name & username)", ctx.UserName, ctx.GrandParentProcessName, ctx.ParentProcessName, ctx.ProcessName)); err != nil || !approved {
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",
Expand All @@ -123,6 +123,7 @@ func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, va

var decryptedName []byte = []byte{}
var decryptedUsername []byte = []byte{}
var decryptedPassword []byte = []byte{}

if !login.Name.IsNull() {
decryptedName, err = crypto.DecryptWith(login.Name, key)
Expand All @@ -140,10 +141,19 @@ func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, va
}
}

if !login.Login.Password.IsNull() {
decryptedPassword, err = crypto.DecryptWith(login.Login.Password, key)
if err != nil {
actionsLog.Warn("Could not decrypt login:" + err.Error())
continue
}
}

decryptedLoginCiphers = append(decryptedLoginCiphers, messages.DecryptedLoginCipher{
Name: string(decryptedName),
Username: string(decryptedUsername),
UUID: login.ID.String(),
Password: string(decryptedPassword),
})

// prevent deadlock from enclaves
Expand Down
19 changes: 18 additions & 1 deletion agent/actions/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,17 @@ func handleUnlockVault(request messages.IPCMessage, cfg *config.Config, vault *v
if err != nil {
fmt.Println(err)
}
safeUserSymmkey, err := crypto.SymmetricEncryptionKeyFromBytes(userSymmkey)

var safeUserSymmkey crypto.SymmetricEncryptionKey
if vault.Keyring.IsMemguard {
safeUserSymmkey, err = crypto.MemguardSymmetricEncryptionKeyFromBytes(userSymmkey)
} else {
safeUserSymmkey, err = crypto.MemorySymmetricEncryptionKeyFromBytes(userSymmkey)
}
if err != nil {
fmt.Println(err)
}

err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, cfg, &safeUserSymmkey, true)
if err != nil {
fmt.Println(err)
Expand Down Expand Up @@ -178,10 +185,20 @@ func handlePinStatus(request messages.IPCMessage, cfg *config.Config, vault *vau
return
}

func handleVaultStatus(request messages.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext *sockets.CallingContext) (response messages.IPCMessage, err error) {
var vaultStatus messages.VaultStatusResponse = messages.VaultStatusResponse{}
vaultStatus.Locked = cfg.IsLocked()
vaultStatus.NumberOfLogins = len(vault.GetLogins())
vaultStatus.NumberOfNotes = len(vault.GetNotes())
response, err = messages.IPCMessageFromPayload(vaultStatus)
return
}

func init() {
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.UnlockVaultRequest{}), handleUnlockVault)
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.LockVaultRequest{}), handleLockVault)
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.WipeVaultRequest{}), handleWipeVault)
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.UpdateVaultPINRequest{}), ensureBiometricsAuthorized(systemauth.AccessVault, handleUpdateVaultPin))
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.GetVaultPINRequest{}), handlePinStatus)
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.VaultStatusRequest{}), handleVaultStatus)
}
5 changes: 2 additions & 3 deletions agent/bitwarden/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"strings"
"time"

"github.com/awnumar/memguard"
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
"github.com/quexten/goldwarden/agent/bitwarden/twofactor"
"github.com/quexten/goldwarden/agent/config"
Expand Down Expand Up @@ -81,7 +80,7 @@ func LoginWithMasterpassword(ctx context.Context, email string, cfg *config.Conf
return LoginResponseToken{}, crypto.MasterKey{}, "", err
}

masterKey, err = crypto.DeriveMasterKey(*memguard.NewBufferFromBytes([]byte(strings.Clone(password))), email, crypto.KDFConfig{Type: crypto.KDFType(preLogin.KDF), Iterations: uint32(preLogin.KDFIterations), Memory: uint32(preLogin.KDFMemory), Parallelism: uint32(preLogin.KDFParallelism)})
masterKey, err = crypto.DeriveMasterKey([]byte(strings.Clone(password)), email, crypto.KDFConfig{Type: crypto.KDFType(preLogin.KDF), Iterations: uint32(preLogin.KDFIterations), Memory: uint32(preLogin.KDFMemory), Parallelism: uint32(preLogin.KDFParallelism)})
if err != nil {
return LoginResponseToken{}, crypto.MasterKey{}, "", err
}
Expand Down Expand Up @@ -127,7 +126,7 @@ func LoginWithDevice(ctx context.Context, email string, cfg *config.Config, vaul
for i := 0; i < 25; i++ {
accessCode += string(alphabet[rand.Intn(len(alphabet))])
}
publicKey, err := crypto.GenerateAsymmetric()
publicKey, err := crypto.GenerateAsymmetric(vault.Keyring.IsMemguard)
if err != nil {
return LoginResponseToken{}, crypto.MasterKey{}, "", err
}
Expand Down
106 changes: 97 additions & 9 deletions agent/bitwarden/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,51 @@ import (

var b64enc = base64.StdEncoding.Strict()

type SymmetricEncryptionKey struct {
type SymmetricEncryptionKey interface {
Bytes() []byte
EncryptionKeyBytes() ([]byte, error)
MacKeyBytes() ([]byte, error)
}

type MemorySymmetricEncryptionKey struct {
encKey []byte
macKey []byte
}

type MemguardSymmetricEncryptionKey struct {
encKey *memguard.Enclave
macKey *memguard.Enclave
}

type AsymmetricEncryptionKey struct {
type AsymmetricEncryptionKey interface {
PublicBytes() []byte
PrivateBytes() ([]byte, error)
}

type MemoryAsymmetricEncryptionKey struct {
encKey []byte
}

type MemguardAsymmetricEncryptionKey struct {
encKey *memguard.Enclave
}

func SymmetricEncryptionKeyFromBytes(key []byte) (SymmetricEncryptionKey, error) {
func MemguardSymmetricEncryptionKeyFromBytes(key []byte) (MemguardSymmetricEncryptionKey, error) {
if len(key) != 64 {
memguard.WipeBytes(key)
return SymmetricEncryptionKey{}, fmt.Errorf("invalid key length: %d", len(key))
return MemguardSymmetricEncryptionKey{}, fmt.Errorf("invalid key length: %d", len(key))
}
return MemguardSymmetricEncryptionKey{memguard.NewEnclave(key[0:32]), memguard.NewEnclave(key[32:64])}, nil
}

func MemorySymmetricEncryptionKeyFromBytes(key []byte) (MemorySymmetricEncryptionKey, error) {
if len(key) != 64 {
return MemorySymmetricEncryptionKey{}, fmt.Errorf("invalid key length: %d", len(key))
}
return SymmetricEncryptionKey{memguard.NewEnclave(key[0:32]), memguard.NewEnclave(key[32:64])}, nil
return MemorySymmetricEncryptionKey{encKey: key[0:32], macKey: key[32:64]}, nil
}

func (key SymmetricEncryptionKey) Bytes() []byte {
func (key MemguardSymmetricEncryptionKey) Bytes() []byte {
k1, err := key.encKey.Open()
if err != nil {
panic(err)
Expand All @@ -50,12 +77,61 @@ func (key SymmetricEncryptionKey) Bytes() []byte {
return keyBytes
}

func AssymmetricEncryptionKeyFromBytes(key []byte) (AsymmetricEncryptionKey, error) {
func (key MemorySymmetricEncryptionKey) Bytes() []byte {
keyBytes := make([]byte, 64)
copy(keyBytes[0:32], key.encKey)
copy(keyBytes[32:64], key.macKey)
return keyBytes
}

func (key MemorySymmetricEncryptionKey) EncryptionKeyBytes() ([]byte, error) {
return key.encKey, nil
}

func (key MemguardSymmetricEncryptionKey) EncryptionKeyBytes() ([]byte, error) {
k, err := key.encKey.Open()
if err != nil {
return nil, err
}
keyBytes := make([]byte, 32)
copy(keyBytes, k.Bytes())
return keyBytes, nil
}

func (key MemorySymmetricEncryptionKey) MacKeyBytes() ([]byte, error) {
return key.macKey, nil
}

func (key MemguardSymmetricEncryptionKey) MacKeyBytes() ([]byte, error) {
k, err := key.macKey.Open()
if err != nil {
return nil, err
}
keyBytes := make([]byte, 32)
copy(keyBytes, k.Bytes())
return keyBytes, nil
}

func MemoryAssymmetricEncryptionKeyFromBytes(key []byte) (MemoryAsymmetricEncryptionKey, error) {
return MemoryAsymmetricEncryptionKey{key}, nil
}

func MemguardAssymmetricEncryptionKeyFromBytes(key []byte) (MemguardAsymmetricEncryptionKey, error) {
k := memguard.NewEnclave(key)
return AsymmetricEncryptionKey{k}, nil
return MemguardAsymmetricEncryptionKey{k}, nil
}

func (key MemoryAsymmetricEncryptionKey) PublicBytes() []byte {
privateKey, err := x509.ParsePKCS8PrivateKey(key.encKey)
pub := (privateKey.(*rsa.PrivateKey)).Public()
publicKeyBytes, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
panic(err)
}
return publicKeyBytes
}

func (key AsymmetricEncryptionKey) PublicBytes() []byte {
func (key MemguardAsymmetricEncryptionKey) PublicBytes() []byte {
buffer, err := key.encKey.Open()
if err != nil {
panic(err)
Expand All @@ -69,6 +145,18 @@ func (key AsymmetricEncryptionKey) PublicBytes() []byte {
return publicKeyBytes
}

func (key MemoryAsymmetricEncryptionKey) PrivateBytes() ([]byte, error) {
return key.encKey, nil
}

func (key MemguardAsymmetricEncryptionKey) PrivateBytes() ([]byte, error) {
buffer, err := key.encKey.Open()
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}

func isMacValid(message, messageMAC, key []byte) bool {
mac := hmac.New(sha256.New, key)
mac.Write(message)
Expand Down
Loading

0 comments on commit 1a91189

Please sign in to comment.