Merge pull request #18 from quexten/feature/gtk-ui

GTK Based Flatpak
This commit is contained in:
Bernd Schoolmann 2023-12-23 13:56:31 +01:00 committed by GitHub
commit 1a91189ffa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 1569 additions and 1635 deletions

5
.gitignore vendored
View file

@ -1,3 +1,4 @@
.vscode
*debug*
goldwarden*
goldwarden
__pycache__
.flatpak-builder

View file

@ -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
@ -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)
@ -176,6 +181,9 @@ 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
@ -183,13 +191,6 @@ By default, the remote desktop portal is used. As long as your desktop environme
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.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 SymmetricEncryptionKey{memguard.NewEnclave(key[0:32]), memguard.NewEnclave(key[32:64])}, nil
return MemguardSymmetricEncryptionKey{memguard.NewEnclave(key[0:32]), memguard.NewEnclave(key[32:64])}, nil
}
func (key SymmetricEncryptionKey) Bytes() []byte {
func MemorySymmetricEncryptionKeyFromBytes(key []byte) (MemorySymmetricEncryptionKey, error) {
if len(key) != 64 {
return MemorySymmetricEncryptionKey{}, fmt.Errorf("invalid key length: %d", len(key))
}
return MemorySymmetricEncryptionKey{encKey: key[0:32], macKey: key[32:64]}, nil
}
func (key MemguardSymmetricEncryptionKey) Bytes() []byte {
k1, err := key.encKey.Open()
if err != nil {
panic(err)
@ -50,12 +77,61 @@ func (key SymmetricEncryptionKey) Bytes() []byte {
return keyBytes
}
func AssymmetricEncryptionKeyFromBytes(key []byte) (AsymmetricEncryptionKey, error) {
k := memguard.NewEnclave(key)
return AsymmetricEncryptionKey{k}, nil
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 AsymmetricEncryptionKey) PublicBytes() []byte {
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 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 MemguardAsymmetricEncryptionKey) PublicBytes() []byte {
buffer, err := key.encKey.Open()
if err != nil {
panic(err)
@ -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)

View file

@ -15,6 +15,8 @@ import (
"fmt"
"io"
"strconv"
"github.com/awnumar/memguard"
)
type EncString struct {
@ -121,16 +123,16 @@ func b64encode(src []byte) []byte {
}
func DecryptWith(s EncString, key SymmetricEncryptionKey) ([]byte, error) {
encKeyData, err := key.encKey.Open()
encKeyData, err := key.EncryptionKeyBytes()
if err != nil {
return nil, err
}
macKeyData, err := key.macKey.Open()
macKeyData, err := key.MacKeyBytes()
if err != nil {
return nil, err
}
block, err := aes.NewCipher(encKeyData.Data())
block, err := aes.NewCipher(encKeyData)
if err != nil {
return nil, err
}
@ -143,13 +145,13 @@ func DecryptWith(s EncString, key SymmetricEncryptionKey) ([]byte, error) {
}
if s.Type == AesCbc256_HmacSha256_B64 {
if len(s.MAC) == 0 || len(macKeyData.Data()) == 0 {
if len(s.MAC) == 0 || len(macKeyData) == 0 {
return nil, fmt.Errorf("decrypt: cipher string type expects a MAC")
}
var msg []byte
msg = append(msg, s.IV...)
msg = append(msg, s.CT...)
if !isMacValid(msg, s.MAC, macKeyData.Data()) {
if !isMacValid(msg, s.MAC, macKeyData) {
return nil, fmt.Errorf("decrypt: MAC mismatch")
}
}
@ -165,14 +167,8 @@ func DecryptWith(s EncString, key SymmetricEncryptionKey) ([]byte, error) {
}
func EncryptWith(data []byte, typ EncStringType, key SymmetricEncryptionKey) (EncString, error) {
encKeyData, err := key.encKey.Open()
if err != nil {
return EncString{}, err
}
macKeyData, err := key.macKey.Open()
if err != nil {
return EncString{}, err
}
encKeyData, err := key.EncryptionKeyBytes()
macKeyData, err := key.MacKeyBytes()
s := EncString{}
switch typ {
@ -183,7 +179,7 @@ func EncryptWith(data []byte, typ EncStringType, key SymmetricEncryptionKey) (En
s.Type = typ
data = padPKCS7(data, aes.BlockSize)
block, err := aes.NewCipher(encKeyData.Bytes())
block, err := aes.NewCipher(encKeyData)
if err != nil {
return s, err
}
@ -196,13 +192,13 @@ func EncryptWith(data []byte, typ EncStringType, key SymmetricEncryptionKey) (En
mode.CryptBlocks(s.CT, data)
if typ == AesCbc256_HmacSha256_B64 {
if len(macKeyData.Bytes()) == 0 {
if len(macKeyData) == 0 {
return s, fmt.Errorf("encrypt: cipher string type expects a MAC")
}
var macMessage []byte
macMessage = append(macMessage, s.IV...)
macMessage = append(macMessage, s.CT...)
mac := hmac.New(sha256.New, macKeyData.Bytes())
mac := hmac.New(sha256.New, macKeyData)
mac.Write(macMessage)
s.MAC = mac.Sum(nil)
}
@ -210,27 +206,31 @@ func EncryptWith(data []byte, typ EncStringType, key SymmetricEncryptionKey) (En
return s, nil
}
func GenerateAsymmetric() (AsymmetricEncryptionKey, error) {
func GenerateAsymmetric(useMemguard bool) (AsymmetricEncryptionKey, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return AsymmetricEncryptionKey{}, err
return MemoryAsymmetricEncryptionKey{}, err
}
encKey, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return AsymmetricEncryptionKey{}, err
return MemoryAsymmetricEncryptionKey{}, err
}
return AssymmetricEncryptionKeyFromBytes(encKey)
if useMemguard {
return MemguardAsymmetricEncryptionKey{memguard.NewEnclave(encKey)}, nil
} else {
return MemoryAsymmetricEncryptionKey{encKey}, nil
}
}
func DecryptWithAsymmetric(s []byte, asymmetrickey AsymmetricEncryptionKey) ([]byte, error) {
key, err := asymmetrickey.encKey.Open()
key, err := asymmetrickey.PrivateBytes()
if err != nil {
return nil, err
}
parsedKey, err := x509.ParsePKCS8PrivateKey(key.Bytes())
parsedKey, err := x509.ParsePKCS8PrivateKey(key)
if err != nil {
return nil, err
}
@ -248,12 +248,12 @@ func DecryptWithAsymmetric(s []byte, asymmetrickey AsymmetricEncryptionKey) ([]b
}
func EncryptWithAsymmetric(s []byte, asymmbetrickey AsymmetricEncryptionKey) ([]byte, error) {
key, err := asymmbetrickey.encKey.Open()
key, err := asymmbetrickey.PrivateBytes()
if err != nil {
return nil, err
}
parsedKey, err := x509.ParsePKIXPublicKey(key.Bytes())
parsedKey, err := x509.ParsePKIXPublicKey(key)
if err != nil {
return nil, err
}

View file

@ -42,21 +42,19 @@ func (masterKey MasterKey) GetBytes() []byte {
return bytes.Clone(buffer.Bytes())
}
func DeriveMasterKey(password memguard.LockedBuffer, email string, kdfConfig KDFConfig) (MasterKey, error) {
func DeriveMasterKey(password []byte, email string, kdfConfig KDFConfig) (MasterKey, error) {
defer debug.FreeOSMemory()
var key []byte
switch kdfConfig.Type {
case PBKDF2:
key = pbkdf2.Key(password.Bytes(), []byte(strings.ToLower(email)), int(kdfConfig.Iterations), 32, sha256.New)
key = pbkdf2.Key(password, []byte(strings.ToLower(email)), int(kdfConfig.Iterations), 32, sha256.New)
case Argon2ID:
var salt [32]byte = sha256.Sum256([]byte(strings.ToLower(email)))
key = argon2.IDKey(password.Bytes(), salt[:], kdfConfig.Iterations, kdfConfig.Memory*1024, uint8(kdfConfig.Parallelism), 32)
key = argon2.IDKey(password, salt[:], kdfConfig.Iterations, kdfConfig.Memory*1024, uint8(kdfConfig.Parallelism), 32)
default:
password.Destroy()
return MasterKey{}, fmt.Errorf("unsupported KDF type %d", kdfConfig.Type)
}
password.Destroy()
return MasterKey{memguard.NewEnclave(key)}, nil
}

View file

@ -9,7 +9,7 @@ import (
"golang.org/x/crypto/hkdf"
)
func InitKeyringFromMasterPassword(keyring *Keyring, accountKey EncString, accountPrivateKey EncString, orgKeys map[string]string, password memguard.LockedBuffer, email string, kdfConfig KDFConfig) error {
func InitKeyringFromMasterPassword(keyring *Keyring, accountKey EncString, accountPrivateKey EncString, orgKeys map[string]string, password []byte, email string, kdfConfig KDFConfig) error {
masterKey, err := DeriveMasterKey(password, email, kdfConfig)
if err != nil {
return err
@ -23,7 +23,7 @@ func InitKeyringFromMasterKey(keyring *Keyring, accountKey EncString, accountPri
switch accountKey.Type {
case AesCbc256_HmacSha256_B64:
stretchedMasterKey, err := stretchKey(masterKey)
stretchedMasterKey, err := stretchKey(masterKey, keyring.IsMemguard)
if err != nil {
return err
}
@ -36,43 +36,56 @@ func InitKeyringFromMasterKey(keyring *Keyring, accountKey EncString, accountPri
return fmt.Errorf("unsupported account key type: %d", accountKey.Type)
}
accountSymmetricKey, err := SymmetricEncryptionKeyFromBytes(accountSymmetricKeyByteArray)
var accountSymmetricKey SymmetricEncryptionKey
var err error
if keyring.IsMemguard {
accountSymmetricKey, err = MemguardSymmetricEncryptionKeyFromBytes(accountSymmetricKeyByteArray)
} else {
accountSymmetricKey, err = MemorySymmetricEncryptionKeyFromBytes(accountSymmetricKeyByteArray)
}
if err != nil {
return err
}
keyring.AccountKey = &accountSymmetricKey
keyring.UnlockWithAccountKey(accountSymmetricKey)
pkcs8PrivateKey, err := DecryptWith(accountPrivateKey, accountSymmetricKey)
if err != nil {
return err
}
keyring.AsymmetricEncyryptionKey = AsymmetricEncryptionKey{memguard.NewEnclave(pkcs8PrivateKey)}
if keyring.IsMemguard {
keyring.AsymmetricEncyryptionKey = MemguardAsymmetricEncryptionKey{memguard.NewEnclave(pkcs8PrivateKey)}
} else {
keyring.AsymmetricEncyryptionKey = MemoryAsymmetricEncryptionKey{pkcs8PrivateKey}
}
keyring.OrganizationKeys = orgKeys
return nil
}
func InitKeyringFromUserSymmetricKey(keyring *Keyring, accountSymmetricKey SymmetricEncryptionKey, accountPrivateKey EncString, orgKeys map[string]string) error {
keyring.AccountKey = &accountSymmetricKey
keyring.UnlockWithAccountKey(accountSymmetricKey)
pkcs8PrivateKey, err := DecryptWith(accountPrivateKey, accountSymmetricKey)
if err != nil {
return err
}
keyring.AsymmetricEncyryptionKey = AsymmetricEncryptionKey{memguard.NewEnclave(pkcs8PrivateKey)}
if keyring.IsMemguard {
keyring.AsymmetricEncyryptionKey = MemguardAsymmetricEncryptionKey{memguard.NewEnclave(pkcs8PrivateKey)}
} else {
keyring.AsymmetricEncyryptionKey = MemoryAsymmetricEncryptionKey{pkcs8PrivateKey}
}
keyring.OrganizationKeys = orgKeys
return nil
}
func stretchKey(masterKey MasterKey) (SymmetricEncryptionKey, error) {
func stretchKey(masterKey MasterKey, useMemguard bool) (SymmetricEncryptionKey, error) {
key := make([]byte, 32)
macKey := make([]byte, 32)
buffer, err := masterKey.encKey.Open()
if err != nil {
return SymmetricEncryptionKey{}, err
return MemorySymmetricEncryptionKey{}, err
}
var r io.Reader
@ -80,5 +93,10 @@ func stretchKey(masterKey MasterKey) (SymmetricEncryptionKey, error) {
r.Read(key)
r = hkdf.Expand(sha256.New, buffer.Data(), []byte("mac"))
r.Read(macKey)
return SymmetricEncryptionKey{memguard.NewEnclave(key), memguard.NewEnclave(macKey)}, nil
if useMemguard {
return MemguardSymmetricEncryptionKey{memguard.NewEnclave(key), memguard.NewEnclave(macKey)}, nil
} else {
return MemorySymmetricEncryptionKey{encKey: key, macKey: macKey}, nil
}
}

View file

@ -2,38 +2,66 @@ package crypto
import (
"errors"
"github.com/quexten/goldwarden/logging"
)
var keyringLog = logging.GetLogger("Goldwarden", "Keyring")
type Keyring struct {
AccountKey *SymmetricEncryptionKey
isLocked bool
accountKey SymmetricEncryptionKey
AsymmetricEncyryptionKey AsymmetricEncryptionKey
IsMemguard bool
OrganizationKeys map[string]string
}
func NewKeyring(accountKey *SymmetricEncryptionKey) Keyring {
func NewMemoryKeyring(accountKey *MemorySymmetricEncryptionKey) Keyring {
keyringLog.Info("Creating new memory keyring")
return Keyring{
AccountKey: accountKey,
isLocked: accountKey == nil,
accountKey: accountKey,
}
}
func NewMemguardKeyring(accountKey *MemguardSymmetricEncryptionKey) Keyring {
keyringLog.Info("Creating new memguard keyring")
return Keyring{
isLocked: accountKey == nil,
accountKey: accountKey,
}
}
func (keyring Keyring) IsLocked() bool {
return keyring.AccountKey == nil
return keyring.isLocked
}
func (keyring *Keyring) Lock() {
keyring.AccountKey = nil
keyring.AsymmetricEncyryptionKey = AsymmetricEncryptionKey{}
keyringLog.Info("Locking keyring")
keyring.isLocked = true
keyring.accountKey = nil
keyring.AsymmetricEncyryptionKey = MemoryAsymmetricEncryptionKey{}
keyring.OrganizationKeys = nil
}
func (keyring *Keyring) UnlockWithAccountKey(accountKey SymmetricEncryptionKey) {
keyringLog.Info("Unlocking keyring with account key")
keyring.isLocked = false
keyring.accountKey = accountKey
}
func (keyring *Keyring) GetAccountKey() SymmetricEncryptionKey {
return keyring.accountKey
}
func (keyring *Keyring) GetSymmetricKeyForOrganization(uuid string) (SymmetricEncryptionKey, error) {
if key, ok := keyring.OrganizationKeys[uuid]; ok {
decryptedOrgKey, err := DecryptWithAsymmetric([]byte(key), keyring.AsymmetricEncyryptionKey)
if err != nil {
return SymmetricEncryptionKey{}, err
return MemorySymmetricEncryptionKey{}, err
}
return SymmetricEncryptionKeyFromBytes(decryptedOrgKey)
return MemorySymmetricEncryptionKeyFromBytes(decryptedOrgKey)
}
return SymmetricEncryptionKey{}, errors.New("no key found for organization")
return MemorySymmetricEncryptionKey{}, errors.New("no key found for organization")
}

View file

@ -150,5 +150,5 @@ func (cipher Cipher) GetKeyForCipher(keyring crypto.Keyring) (crypto.SymmetricEn
if cipher.OrganizationID != nil {
return keyring.GetSymmetricKeyForOrganization(cipher.OrganizationID.String())
}
return *keyring.AccountKey, nil
return keyring.GetAccountKey(), nil
}

View file

@ -69,7 +69,7 @@ func CreateAuthResponse(ctx context.Context, authRequest AuthRequestData, keyrin
//}
publicKey, err := base64.StdEncoding.DecodeString(authRequest.PublicKey)
requesterKey, err := crypto.AssymmetricEncryptionKeyFromBytes(publicKey)
requesterKey, err := crypto.MemoryAssymmetricEncryptionKeyFromBytes(publicKey)
encryptedUserSymmetricKey, err := crypto.EncryptWithAsymmetric(userSymmetricKey, requesterKey)
if err != nil {

View file

@ -30,35 +30,42 @@ func Sync(ctx context.Context, config *config.Config) (models.SyncData, error) {
func DoFullSync(ctx context.Context, vault *vault.Vault, config *config.Config, userSymmetricKey *crypto.SymmetricEncryptionKey, allowCache bool) error {
log.Info("Performing full sync...")
sync, err := Sync(ctx, config)
if err != nil {
log.Error("Could not sync: %v", err)
if allowCache {
home, _ := os.UserHomeDir()
sync, err = ReadVault(home + path)
if err != nil {
return err
}
} else {
return err
}
} else {
log.Info("Sync successful, initializing keyring and vault...")
}
var orgKeys map[string]string = make(map[string]string)
log.Info("Reading %d org keys...", len(sync.Profile.Organizations))
for _, org := range sync.Profile.Organizations {
orgId := org.Id.String()
orgKeys[orgId] = org.Key
}
if userSymmetricKey != nil {
log.Info("Initializing keyring from user symmetric key...")
crypto.InitKeyringFromUserSymmetricKey(vault.Keyring, *userSymmetricKey, sync.Profile.PrivateKey, orgKeys)
}
log.Info("Clearing vault...")
vault.Clear()
log.Info("Adding %d ciphers to vault...", len(sync.Ciphers))
for _, cipher := range sync.Ciphers {
switch cipher.Type {
case models.CipherLogin:
vault.AddOrUpdateLogin(cipher)
break
case models.CipherNote:
vault.AddOrUpdateSecureNote(cipher)
break
}
}

View file

@ -3,7 +3,6 @@ package bitwarden
import (
"bytes"
"context"
"fmt"
"net/url"
"os"
"os/signal"
@ -49,14 +48,14 @@ const (
)
const (
WEBSOCKET_SLEEP_DURATION_SECONDS = 5
WEBSOCKET_SLEEP_DURATION_SECONDS = 60
)
func RunWebsocketDaemon(ctx context.Context, vault *vault.Vault, cfg *config.Config) {
time.Sleep(5 * time.Second)
for {
time.Sleep(WEBSOCKET_SLEEP_DURATION_SECONDS * time.Second)
if cfg.IsLocked() {
time.Sleep(5 * time.Second)
continue
}
@ -66,6 +65,8 @@ func RunWebsocketDaemon(ctx context.Context, vault *vault.Vault, cfg *config.Con
websocketLog.Error("Websocket error %s", err)
}
}
time.Sleep(WEBSOCKET_SLEEP_DURATION_SECONDS * time.Second)
}
}
@ -95,12 +96,15 @@ func connectToWebsocket(ctx context.Context, vault *vault.Vault, cfg *config.Con
go func() {
defer close(done)
for {
mt, message, err := c.ReadMessage()
fmt.Println(mt)
_, message, err := c.ReadMessage()
if err != nil {
websocketLog.Error("Error reading websocket message %s", err)
return
}
if len(message) < 3 {
//ignore empty messages
continue
}
if messageType, cipherid, success := websocketMessageType(message); success {
var mt1 = NotificationMessageType(messageType)
@ -169,7 +173,11 @@ func connectToWebsocket(ctx context.Context, vault *vault.Vault, cfg *config.Con
break
case LogOut:
websocketLog.Info("LogOut received. Wiping vault and exiting...")
memguard.SafeExit(0)
if vault.Keyring.IsMemguard {
memguard.SafeExit(0)
} else {
os.Exit(0)
}
case AuthRequest:
websocketLog.Info("AuthRequest received" + string(cipherid))
authRequest, err := GetAuthRequest(context.WithValue(ctx, AuthToken{}, token.AccessToken), cipherid, cfg)

View file

@ -1,6 +1,7 @@
package config
import (
"bytes"
cryptoSubtle "crypto/subtle"
"encoding/base64"
"encoding/hex"
@ -10,7 +11,6 @@ import (
"runtime/debug"
"sync"
"github.com/awnumar/memguard"
"github.com/google/uuid"
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
@ -43,6 +43,7 @@ type RuntimeConfig struct {
User string
Password string
Pin string
UseMemguard bool
}
type ConfigFile struct {
@ -67,15 +68,18 @@ type LoginToken struct {
}
type Config struct {
key *memguard.LockedBuffer
ConfigFile ConfigFile
mu sync.Mutex
useMemguard bool
key *LockedBuffer
ConfigFile ConfigFile
mu sync.Mutex
}
func DefaultConfig() Config {
func DefaultConfig(useMemguard bool) Config {
deviceUUID, _ := uuid.NewUUID()
keyBuffer := NewBuffer(32, useMemguard)
return Config{
memguard.NewBuffer(32),
useMemguard,
&keyBuffer,
ConfigFile{
IdentityUrl: "https://vault.bitwarden.com/identity",
ApiUrl: "https://vault.bitwarden.com/api",
@ -93,7 +97,8 @@ func DefaultConfig() Config {
}
func (c *Config) IsLocked() bool {
return c.key.EqualTo(make([]byte, 32)) && c.HasPin()
key := (*c.key).Bytes()
return bytes.Equal(key, make([]byte, 32)) && c.HasPin()
}
func (c *Config) IsLoggedIn() bool {
@ -116,7 +121,8 @@ func (c *Config) Unlock(password string) bool {
return false
}
c.key = memguard.NewBufferFromBytes(key)
keyBuffer := NewBufferFromBytes(key, c.useMemguard)
c.key = &keyBuffer
return true
}
@ -139,7 +145,7 @@ func (c *Config) Lock() {
if c.IsLocked() {
return
}
c.key.Wipe()
(*c.key).Wipe()
}
func (c *Config) Purge() {
@ -151,7 +157,8 @@ func (c *Config) Purge() {
c.ConfigFile.EncryptedUserSymmetricKey = ""
c.ConfigFile.ConfigKeyHash = ""
c.ConfigFile.EncryptedMasterKey = ""
c.key = memguard.NewBuffer(32)
key := NewBuffer(32, c.useMemguard)
c.key = &key
}
func (c *Config) HasPin() bool {
@ -173,7 +180,8 @@ func (c *Config) UpdatePin(password string, write bool) {
plaintextEncryptedMasterPasswordHash, err4 := c.decryptString(c.ConfigFile.EncryptedMasterPasswordHash)
plaintextMasterKey, err5 := c.decryptString(c.ConfigFile.EncryptedMasterKey)
c.key = memguard.NewBufferFromBytes(newKey)
key := NewBufferFromBytes(newKey, c.useMemguard)
c.key = &key
if err1 == nil {
c.ConfigFile.EncryptedToken, err1 = c.encryptString(plaintextToken)
@ -314,7 +322,7 @@ func (c *Config) encryptString(data string) (string, error) {
if c.IsLocked() {
return "", errors.New("config is locked")
}
ca, err := subtle.NewChaCha20Poly1305(c.key.Bytes())
ca, err := subtle.NewChaCha20Poly1305((*c.key).Bytes())
if err != nil {
return "", err
}
@ -336,7 +344,7 @@ func (c *Config) decryptString(data string) (string, error) {
return "", err
}
ca, err := subtle.NewChaCha20Poly1305(c.key.Bytes())
ca, err := subtle.NewChaCha20Poly1305((*c.key).Bytes())
if err != nil {
return "", err
}
@ -378,8 +386,9 @@ func (config *Config) WriteConfig() error {
func ReadConfig(rtCfg RuntimeConfig) (Config, error) {
file, err := os.Open(rtCfg.ConfigDirectory)
if err != nil {
key := NewBuffer(32, rtCfg.UseMemguard)
return Config{
key: memguard.NewBuffer(32),
key: &key,
ConfigFile: ConfigFile{},
}, err
}
@ -389,19 +398,22 @@ func ReadConfig(rtCfg RuntimeConfig) (Config, error) {
config := ConfigFile{}
err = decoder.Decode(&config)
if err != nil {
key := NewBuffer(32, rtCfg.UseMemguard)
return Config{
key: memguard.NewBuffer(32),
key: &key,
ConfigFile: ConfigFile{},
}, err
}
if config.ConfigKeyHash == "" {
key := NewBuffer(32, rtCfg.UseMemguard)
return Config{
key: memguard.NewBuffer(32),
key: &key,
ConfigFile: config,
}, nil
}
key := NewBuffer(32, rtCfg.UseMemguard)
return Config{
key: memguard.NewBuffer(32),
key: &key,
ConfigFile: config,
}, nil
}
@ -419,11 +431,17 @@ func (cfg *Config) TryUnlock(vault *vault.Vault) error {
if cfg.IsLoggedIn() {
userKey, err := cfg.GetUserSymmetricKey()
if err == nil {
key, err := crypto.SymmetricEncryptionKeyFromBytes(userKey)
var key crypto.SymmetricEncryptionKey
var err error
if vault.Keyring.IsMemguard {
key, err = crypto.MemguardSymmetricEncryptionKeyFromBytes(userKey)
} else {
key, err = crypto.MemorySymmetricEncryptionKeyFromBytes(userKey)
}
if err != nil {
return err
}
vault.Keyring.AccountKey = &key
vault.Keyring.UnlockWithAccountKey(key)
} else {
cfg.Lock()
return err

View file

@ -0,0 +1,48 @@
package config
import "github.com/awnumar/memguard"
type LockedBuffer interface {
Bytes() []byte
Wipe()
}
func NewBuffer(size int, useMemguard bool) LockedBuffer {
if useMemguard {
return MemGuardLockedBuffer{memguard.NewBuffer(size)}
}
return MemoryLockedBuffer{make([]byte, size)}
}
func NewBufferFromBytes(bytes []byte, useMemguard bool) LockedBuffer {
if useMemguard {
return MemGuardLockedBuffer{memguard.NewBufferFromBytes(bytes)}
}
return MemoryLockedBuffer{bytes}
}
type MemGuardLockedBuffer struct {
buffer *memguard.LockedBuffer
}
func (b MemGuardLockedBuffer) Wipe() {
b.buffer.Destroy()
}
func (b MemGuardLockedBuffer) Bytes() []byte {
return b.buffer.Bytes()
}
type MemoryLockedBuffer struct {
buffer []byte
}
func (b MemoryLockedBuffer) Wipe() {
for i := range b.buffer {
b.buffer[i] = 0
}
}
func (b MemoryLockedBuffer) Bytes() []byte {
return b.buffer
}

View file

@ -1,4 +1,4 @@
//go:build !windows && !darwin && !linux
//go:build (!windows && !darwin && !linux) || noautofill
package agent

View file

@ -1,4 +1,4 @@
//go:build windows || darwin || linux
//go:build (windows || darwin || linux) && !noautofill
package agent

View file

@ -20,11 +20,26 @@ type CallingContext struct {
func GetCallingContext(connection net.Conn) CallingContext {
creds, err := peercred.Get(connection)
errorContext := CallingContext{
UserName: "unknown",
ProcessName: "unknown",
ParentProcessName: "unknown",
GrandParentProcessName: "unknown",
ProcessPid: 0,
ParentProcessPid: 0,
GrandParentProcessPid: 0,
}
if err != nil {
panic(err)
return errorContext
}
pid, _ := creds.PID()
process, err := gops.FindProcess(pid)
if err != nil {
return errorContext
}
if process == nil {
return errorContext
}
// git is epheremal and spawns ssh-keygen and ssh so we need to anchor to git
if process.Executable() == "ssh-keygen" || process.Executable() == "ssh" {
@ -38,22 +53,22 @@ func GetCallingContext(connection net.Conn) CallingContext {
uid, _ := creds.UserID()
ppid := process.PPid()
if err != nil {
panic(err)
return errorContext
}
parentProcess, err := gops.FindProcess(ppid)
if err != nil {
panic(err)
return errorContext
}
parentParentProcess, err := gops.FindProcess(parentProcess.PPid())
if err != nil {
panic(err)
return errorContext
}
username, err := user.LookupId(uid)
if err != nil {
panic(err)
return errorContext
}
return CallingContext{

View file

@ -30,13 +30,13 @@ func NewSSHKeyCipher(name string, keyring *crypto.Keyring) (models.Cipher, strin
privatePEM := pem.EncodeToMemory(&privBlock)
publicKey, err := ssh.NewPublicKey(pub)
encryptedName, _ := crypto.EncryptWith([]byte(name), crypto.AesCbc256_HmacSha256_B64, *keyring.AccountKey)
encryptedPublicKeyKey, _ := crypto.EncryptWith([]byte("public-key"), crypto.AesCbc256_HmacSha256_B64, *keyring.AccountKey)
encryptedPublicKeyValue, _ := crypto.EncryptWith([]byte(string(ssh.MarshalAuthorizedKey(publicKey))), crypto.AesCbc256_HmacSha256_B64, *keyring.AccountKey)
encryptedCustomTypeKey, _ := crypto.EncryptWith([]byte("custom-type"), crypto.AesCbc256_HmacSha256_B64, *keyring.AccountKey)
encryptedCustomTypeValue, _ := crypto.EncryptWith([]byte("ssh-key"), crypto.AesCbc256_HmacSha256_B64, *keyring.AccountKey)
encryptedPrivateKeyKey, _ := crypto.EncryptWith([]byte("private-key"), crypto.AesCbc256_HmacSha256_B64, *keyring.AccountKey)
encryptedPrivateKeyValue, _ := crypto.EncryptWith(privatePEM, crypto.AesCbc256_HmacSha256_B64, *keyring.AccountKey)
encryptedName, _ := crypto.EncryptWith([]byte(name), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedPublicKeyKey, _ := crypto.EncryptWith([]byte("public-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedPublicKeyValue, _ := crypto.EncryptWith([]byte(string(ssh.MarshalAuthorizedKey(publicKey))), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedCustomTypeKey, _ := crypto.EncryptWith([]byte("custom-type"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedCustomTypeValue, _ := crypto.EncryptWith([]byte("ssh-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedPrivateKeyKey, _ := crypto.EncryptWith([]byte("private-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedPrivateKeyValue, _ := crypto.EncryptWith(privatePEM, crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
cipher := models.Cipher{
Type: models.CipherNote,

View file

@ -78,15 +78,18 @@ func BiometricsWorking() bool {
authority, err := polkit.NewAuthority()
if err != nil {
log.Warn("Failed to create polkit authority: %s", err.Error())
return false
}
result, err := authority.EnumerateActions("en")
if err != nil {
log.Warn("Failed to enumerate polkit actions: %s", err.Error())
return false
}
if len(result) == 0 {
log.Warn("No polkit actions found")
return false
}

View file

@ -87,6 +87,7 @@ func GetPermission(sessionType SessionType, ctx sockets.CallingContext, config *
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
@ -96,10 +97,10 @@ func GetPermission(sessionType SessionType, ctx sockets.CallingContext, config *
}
}
approval, err := pinentry.GetApproval("Goldwarden authorization", message)
if err != nil || !approval {
return false, err
}
// approval, err := pinentry.GetApproval("Goldwarden authorization", message)
// if err != nil || !approval {
// return false, err
// }
log.Info("Permission granted, creating session")
sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid, sessionType)

View file

@ -99,13 +99,18 @@ type AgentState struct {
func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
ctx := context.Background()
// check if exists
keyring := crypto.NewKeyring(nil)
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 {
log.Warn("Could not read config: %s", err.Error())
cfg = config.DefaultConfig()
cfg = config.DefaultConfig(runtimeConfig.UseMemguard)
cfg.ConfigFile.RuntimeConfig = runtimeConfig
cfg.WriteConfig()
}
@ -137,7 +142,12 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
time.Sleep(60 * time.Second)
continue
}
protectedUserSymetricKey, err := crypto.SymmetricEncryptionKeyFromBytes(userSymmetricKey)
var protectedUserSymetricKey crypto.SymmetricEncryptionKey
if vault.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 {
@ -179,7 +189,12 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
if err != nil {
fmt.Println(err)
}
protectedUserSymetricKey, err := crypto.SymmetricEncryptionKeyFromBytes(userSymmetricKey)
var protectedUserSymetricKey crypto.SymmetricEncryptionKey
if vault.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 {

View file

@ -7,10 +7,12 @@ import (
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
"github.com/quexten/goldwarden/agent/bitwarden/models"
"github.com/rs/zerolog/log"
"github.com/quexten/goldwarden/logging"
"golang.org/x/exp/slices"
)
var vaultLog = logging.GetLogger("Goldwarden", "Vault")
type Vault struct {
Keyring *crypto.Keyring
logins map[string]models.Cipher
@ -94,7 +96,7 @@ func (vault *Vault) isEnv(cipher models.Cipher) (string, bool) {
key, err := cipher.GetKeyForCipher(*vault.Keyring)
if err != nil {
log.Warn().Err(err).Msg("Failed to get key for cipher " + cipher.ID.String())
vaultLog.Error("Failed to get key for cipher "+cipher.ID.String(), err.Error())
return "", false
}
@ -132,7 +134,7 @@ func (vault *Vault) isSSHKey(cipher models.Cipher) bool {
key, err := cipher.GetKeyForCipher(*vault.Keyring)
if err != nil {
log.Warn().Err(err).Msg("Failed to get key for cipher " + cipher.ID.String())
vaultLog.Error("Failed to get key for cipher "+cipher.ID.String(), err.Error())
return false
}
@ -140,8 +142,12 @@ func (vault *Vault) isSSHKey(cipher models.Cipher) bool {
fieldName, err := crypto.DecryptWith(field.Name, key)
if err != nil {
cipherID := cipher.ID.String()
orgID := cipher.OrganizationID.String()
log.Warn().Err(err).Msg("Failed to decrypt field name with on cipher " + cipherID + " in organization " + orgID)
if cipher.OrganizationID != nil {
orgID := cipher.OrganizationID.String()
vaultLog.Error("Failed to decrypt field name with on cipher "+cipherID+" in organization "+orgID, err.Error())
} else {
vaultLog.Error("Failed to decrypt field name with on cipher "+cipherID, err.Error())
}
continue
}
fieldValue, err := crypto.DecryptWith(field.Value, key)
@ -229,7 +235,7 @@ func (vault *Vault) GetEnvCredentialForExecutable(executableName string) (map[st
if id, ok := vault.envCredentials[executableName]; ok {
key, err := vault.secureNotes[id].GetKeyForCipher(*vault.Keyring)
if err != nil {
log.Warn().Err(err).Msg("Failed to get key for cipher " + id)
vaultLog.Error("Failed to get key for cipher " + id)
return make(map[string]string), false
}
@ -302,7 +308,7 @@ func (vault *Vault) GetLoginByFilter(uuid string, orgId string, name string, use
key, err := cipher.GetKeyForCipher(*vault.Keyring)
if err != nil {
log.Warn().Err(err).Msg("Failed to get key for cipher " + cipher.ID.String())
vaultLog.Error("Failed to get key for cipher " + cipher.ID.String())
continue
}
if name != "" {
@ -312,7 +318,7 @@ func (vault *Vault) GetLoginByFilter(uuid string, orgId string, name string, use
decryptedName, err := crypto.DecryptWith(cipher.Name, key)
if err != nil {
log.Warn().Err(err).Msg("Failed to decrypt name for cipher " + cipher.ID.String())
vaultLog.Error("Failed to decrypt name for cipher " + cipher.ID.String())
continue
}
if name != "" && string(decryptedName) != name {
@ -327,7 +333,7 @@ func (vault *Vault) GetLoginByFilter(uuid string, orgId string, name string, use
decryptedUsername, err := crypto.DecryptWith(cipher.Login.Username, key)
if err != nil {
log.Warn().Err(err).Msg("Failed to decrypt username for cipher " + cipher.ID.String())
vaultLog.Error("Failed to decrypt username for cipher " + cipher.ID.String())
continue
}
if username != "" && string(decryptedUsername) != username {
@ -338,7 +344,7 @@ func (vault *Vault) GetLoginByFilter(uuid string, orgId string, name string, use
return cipher, nil
}
return models.Cipher{}, errors.New("Cipher not found")
return models.Cipher{}, errors.New("cipher not found")
}
func (vault *Vault) GetNoteByFilter(uuid string, orgId string, name string) (models.Cipher, error) {
@ -355,12 +361,12 @@ func (vault *Vault) GetNoteByFilter(uuid string, orgId string, name string) (mod
key, err := cipher.GetKeyForCipher(*vault.Keyring)
if err != nil {
log.Warn().Err(err).Msg("Failed to get key for cipher " + cipher.ID.String())
vaultLog.Error("Failed to get key for cipher "+cipher.ID.String(), err.Error())
continue
}
decryptedName, err := crypto.DecryptWith(cipher.Name, key)
if err != nil {
log.Warn().Err(err).Msg("Failed to decrypt name for cipher " + cipher.ID.String())
vaultLog.Error("Failed to decrypt name for cipher "+cipher.ID.String(), err.Error())
continue
}
if name != "" && string(decryptedName) != name {

View file

@ -74,12 +74,17 @@ func serveVirtualAgent(recv chan []byte, send chan []byte, ctx context.Context,
func StartVirtualAgent(runtimeConfig config.RuntimeConfig) (chan []byte, chan []byte) {
ctx := context.Background()
// check if exists
keyring := crypto.NewKeyring(nil)
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()
var cfg = config.DefaultConfig(runtimeConfig.UseMemguard)
cfg.WriteConfig()
}
cfg.ConfigFile.RuntimeConfig = runtimeConfig
@ -106,7 +111,12 @@ func StartVirtualAgent(runtimeConfig config.RuntimeConfig) (chan []byte, chan []
if err != nil {
fmt.Println(err)
}
protectedUserSymetricKey, err := crypto.SymmetricEncryptionKeyFromBytes(userSymmetricKey)
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 {

View file

@ -1,78 +0,0 @@
//go:build !noautofill
package autofill
import (
"errors"
"github.com/atotto/clipboard"
"github.com/quexten/goldwarden/autofill/autotype"
"github.com/quexten/goldwarden/client"
"github.com/quexten/goldwarden/ipc/messages"
)
func GetLoginByUUID(uuid string, client client.Client) (messages.DecryptedLoginCipher, error) {
resp, err := client.SendToAgent(messages.GetLoginRequest{
UUID: uuid,
})
if err != nil {
return messages.DecryptedLoginCipher{}, err
}
switch resp.(type) {
case messages.GetLoginResponse:
castedResponse := (resp.(messages.GetLoginResponse))
return castedResponse.Result, nil
case messages.ActionResponse:
castedResponse := (resp.(messages.ActionResponse))
return messages.DecryptedLoginCipher{}, errors.New("Error: " + castedResponse.Message)
default:
return messages.DecryptedLoginCipher{}, errors.New("Wrong response type")
}
}
func ListLogins(client client.Client) ([]messages.DecryptedLoginCipher, error) {
resp, err := client.SendToAgent(messages.ListLoginsRequest{})
if err != nil {
return []messages.DecryptedLoginCipher{}, err
}
switch resp.(type) {
case messages.GetLoginsResponse:
castedResponse := (resp.(messages.GetLoginsResponse))
return castedResponse.Result, nil
case messages.ActionResponse:
castedResponse := (resp.(messages.ActionResponse))
return []messages.DecryptedLoginCipher{}, errors.New("Error: " + castedResponse.Message)
default:
return []messages.DecryptedLoginCipher{}, errors.New("Wrong response type")
}
}
func Run(layout string, client client.Client) {
logins, err := ListLogins(client)
if err != nil {
panic(err)
}
autofillEntries := []AutofillEntry{}
for _, login := range logins {
autofillEntries = append(autofillEntries, AutofillEntry{
Name: login.Name,
Username: login.Username,
UUID: login.UUID,
})
}
RunAutofill(autofillEntries, func(uuid string, c chan bool) {
login, err := GetLoginByUUID(uuid, client)
if err != nil {
panic(err)
}
autotype.TypeString(string(login.Username)+"\t"+string(login.Password), layout)
clipboard.WriteAll(login.TwoFactorCode)
c <- true
})
}

View file

@ -1,384 +0,0 @@
package uinput
import (
"errors"
"fmt"
"github.com/bendahl/uinput"
)
type Dvorak struct {
}
func (d Dvorak) TypeKey(key Key, keyboard uinput.Keyboard) error {
var err error
switch key {
case KeyA:
err = keyboard.KeyPress(uinput.KeyA)
break
case KeyAUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyA)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyB:
err = keyboard.KeyPress(uinput.KeyN)
break
case KeyBUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyN)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyC:
err = keyboard.KeyPress(uinput.KeyI)
break
case KeyCUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyI)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyD:
err = keyboard.KeyPress(uinput.KeyH)
break
case KeyDUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyH)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyE:
err = keyboard.KeyPress(uinput.KeyD)
break
case KeyEUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
err = keyboard.KeyPress(uinput.KeyD)
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyF:
err = keyboard.KeyPress(uinput.KeyY)
break
case KeyFUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyY)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyG:
err = keyboard.KeyPress(uinput.KeyU)
break
case KeyGUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyU)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyH:
err = keyboard.KeyPress(uinput.KeyJ)
break
case KeyHUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyJ)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyI:
err = keyboard.KeyPress(uinput.KeyG)
break
case KeyIUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyG)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyJ:
err = keyboard.KeyPress(uinput.KeyC)
break
case KeyJUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyC)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyK:
err = keyboard.KeyPress(uinput.KeyV)
break
case KeyKUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyV)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyL:
err = keyboard.KeyPress(uinput.KeyP)
break
case KeyLUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyP)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyM:
err = keyboard.KeyPress(uinput.KeyM)
break
case KeyMUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyM)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyN:
err = keyboard.KeyPress(uinput.KeyL)
break
case KeyNUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyL)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyO:
err = keyboard.KeyPress(uinput.KeyS)
break
case KeyOUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyS)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyP:
err = keyboard.KeyPress(uinput.KeyR)
break
case KeyPUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyR)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyQ:
err = keyboard.KeyPress(uinput.KeyX)
break
case KeyQUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyX)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyR:
err = keyboard.KeyPress(uinput.KeyO)
break
case KeyRUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyO)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyS:
err = keyboard.KeyPress(uinput.KeySemicolon)
break
case KeySUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeySemicolon)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyT:
err = keyboard.KeyPress(uinput.KeyK)
break
case KeyTUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyK)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyU:
err = keyboard.KeyPress(uinput.KeyF)
break
case KeyUUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyF)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyV:
err = keyboard.KeyPress(uinput.KeyDot)
break
case KeyVUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyDot)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyW:
err = keyboard.KeyPress(uinput.KeyComma)
break
case KeyWUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyComma)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyX:
err = keyboard.KeyPress(uinput.KeyB)
break
case KeyXUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyB)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyY:
err = keyboard.KeyPress(uinput.KeyT)
break
case KeyYUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyT)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyZ:
err = keyboard.KeyPress(uinput.KeySlash)
break
case KeyZUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeySlash)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case Key1:
err = keyboard.KeyPress(uinput.Key1)
break
case Key2:
err = keyboard.KeyPress(uinput.Key2)
break
case Key3:
err = keyboard.KeyPress(uinput.Key3)
break
case Key4:
err = keyboard.KeyPress(uinput.Key4)
break
case Key5:
err = keyboard.KeyPress(uinput.Key5)
break
case Key6:
err = keyboard.KeyPress(uinput.Key6)
break
case Key7:
err = keyboard.KeyPress(uinput.Key7)
break
case Key8:
err = keyboard.KeyPress(uinput.Key8)
break
case Key9:
err = keyboard.KeyPress(uinput.Key9)
break
case Key0:
err = keyboard.KeyPress(uinput.Key0)
break
case KeyTab:
err = keyboard.KeyPress(uinput.KeyTab)
break
case KeyHyphen:
err = keyboard.KeyPress(uinput.KeyApostrophe)
break
case KeyExclamationMark:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key1)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyAtSign:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key2)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyHash:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key3)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyDollar:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key4)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyPercent:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key5)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyCaret:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key6)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyAmpersand:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key7)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyAsterisk:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key8)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyDot:
err = keyboard.KeyPress(uinput.KeyE)
break
case KeyComma:
err = keyboard.KeyPress(uinput.KeyW)
break
case KeyQuestionMark:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyLeftbrace)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeySemicolon:
err = keyboard.KeyPress(uinput.KeyZ)
break
case KeyColon:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyZ)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeySlash:
err = keyboard.KeyPress(uinput.KeyLeftbrace)
break
case KeyApostrophe:
err = keyboard.KeyPress(uinput.KeyQ)
break
case KeySpace:
err = keyboard.KeyPress(uinput.KeySpace)
break
default:
fmt.Println("Unknown key: ", key)
fmt.Println("Please add it to the dvorak layout")
return errors.New("Unknown key")
}
return err
}
func init() {
DefaultLayoutRegistry.Register("dvorak", Dvorak{})
}

View file

@ -1,387 +0,0 @@
package uinput
import (
"errors"
"fmt"
"github.com/bendahl/uinput"
)
type Qwerty struct {
}
func (d Qwerty) TypeKey(key Key, keyboard uinput.Keyboard) error {
var err error
switch key {
case KeyA:
err = keyboard.KeyPress(uinput.KeyA)
break
case KeyAUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyA)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyB:
err = keyboard.KeyPress(uinput.KeyB)
break
case KeyBUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyB)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyC:
err = keyboard.KeyPress(uinput.KeyC)
break
case KeyCUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyC)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyD:
err = keyboard.KeyPress(uinput.KeyD)
break
case KeyDUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyD)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyE:
err = keyboard.KeyPress(uinput.KeyE)
break
case KeyEUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyE)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyF:
err = keyboard.KeyPress(uinput.KeyF)
break
case KeyFUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyF)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyG:
err = keyboard.KeyPress(uinput.KeyG)
break
case KeyGUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyG)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyH:
err = keyboard.KeyPress(uinput.KeyH)
break
case KeyHUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyH)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyI:
err = keyboard.KeyPress(uinput.KeyI)
break
case KeyIUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyI)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyJ:
err = keyboard.KeyPress(uinput.KeyJ)
break
case KeyJUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyJ)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyK:
err = keyboard.KeyPress(uinput.KeyK)
break
case KeyKUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyK)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyL:
err = keyboard.KeyPress(uinput.KeyL)
break
case KeyLUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyL)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyM:
err = keyboard.KeyPress(uinput.KeyM)
break
case KeyMUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyM)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyN:
err = keyboard.KeyPress(uinput.KeyN)
break
case KeyNUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyN)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyO:
err = keyboard.KeyPress(uinput.KeyO)
break
case KeyOUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyO)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyP:
err = keyboard.KeyPress(uinput.KeyP)
break
case KeyPUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyP)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyQ:
err = keyboard.KeyPress(uinput.KeyQ)
break
case KeyQUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyQ)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyR:
err = keyboard.KeyPress(uinput.KeyR)
break
case KeyRUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyR)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyS:
err = keyboard.KeyPress(uinput.KeyS)
break
case KeySUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyS)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyT:
err = keyboard.KeyPress(uinput.KeyT)
break
case KeyTUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyT)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyU:
err = keyboard.KeyPress(uinput.KeyU)
break
case KeyUUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyU)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyV:
err = keyboard.KeyPress(uinput.KeyV)
break
case KeyVUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyV)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyW:
err = keyboard.KeyPress(uinput.KeyW)
break
case KeyWUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyW)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyX:
err = keyboard.KeyPress(uinput.KeyX)
break
case KeyXUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyX)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyY:
err = keyboard.KeyPress(uinput.KeyY)
break
case KeyYUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyY)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case KeyZ:
err = keyboard.KeyPress(uinput.KeyZ)
break
case KeyZUpper:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeyZ)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
case Key1:
err = keyboard.KeyPress(uinput.Key1)
break
case Key2:
err = keyboard.KeyPress(uinput.Key2)
break
case Key3:
err = keyboard.KeyPress(uinput.Key3)
break
case Key4:
err = keyboard.KeyPress(uinput.Key4)
break
case Key5:
err = keyboard.KeyPress(uinput.Key5)
break
case Key6:
err = keyboard.KeyPress(uinput.Key6)
break
case Key7:
err = keyboard.KeyPress(uinput.Key7)
break
case Key8:
err = keyboard.KeyPress(uinput.Key8)
break
case Key9:
err = keyboard.KeyPress(uinput.Key9)
break
case Key0:
err = keyboard.KeyPress(uinput.Key0)
break
case KeyTab:
err = keyboard.KeyPress(uinput.KeyTab)
break
case KeyHyphen:
err = keyboard.KeyPress(uinput.KeyMinus)
break
case KeyExclamationMark:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key1)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyAtSign:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key2)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyHash:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key3)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyDollar:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key4)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyPercent:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key5)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyCaret:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key6)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyAmpersand:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key7)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyAsterisk:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.Key8)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeyDot:
err = keyboard.KeyPress(uinput.KeyDot)
break
case KeyComma:
err = keyboard.KeyPress(uinput.KeyW)
break
case KeyQuestionMark:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeySlash)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeySemicolon:
err = keyboard.KeyPress(uinput.KeySemicolon)
break
case KeyColon:
err = keyboard.KeyDown(uinput.KeyLeftshift)
Sleep()
err = keyboard.KeyPress(uinput.KeySemicolon)
Sleep()
err = keyboard.KeyUp(uinput.KeyLeftshift)
break
case KeySlash:
err = keyboard.KeyPress(uinput.KeySlash)
break
case KeyApostrophe:
err = keyboard.KeyPress(uinput.KeyApostrophe)
break
case KeySpace:
err = keyboard.KeyPress(uinput.KeySpace)
break
default:
fmt.Println("Unknown key: ", key)
fmt.Println("Please add it to the QWERTY layout")
return errors.New("Unknown key")
}
return err
}
func init() {
DefaultLayoutRegistry.Register("qwerty", Qwerty{})
}

View file

@ -1,175 +0,0 @@
package uinput
import (
"errors"
"fmt"
"time"
"github.com/bendahl/uinput"
)
type Layout interface {
TypeKey(key Key, keyboard uinput.Keyboard) error
}
type Key string
const (
KeyA Key = "a"
KeyB Key = "b"
KeyC Key = "c"
KeyD Key = "d"
KeyE Key = "e"
KeyF Key = "f"
KeyG Key = "g"
KeyH Key = "h"
KeyI Key = "i"
KeyJ Key = "j"
KeyK Key = "k"
KeyL Key = "l"
KeyM Key = "m"
KeyN Key = "n"
KeyO Key = "o"
KeyP Key = "p"
KeyQ Key = "q"
KeyR Key = "r"
KeyS Key = "s"
KeyT Key = "t"
KeyU Key = "u"
KeyV Key = "v"
KeyW Key = "w"
KeyX Key = "x"
KeyY Key = "y"
KeyZ Key = "z"
KeyAUpper Key = "A"
KeyBUpper Key = "B"
KeyCUpper Key = "C"
KeyDUpper Key = "D"
KeyEUpper Key = "E"
KeyFUpper Key = "F"
KeyGUpper Key = "G"
KeyHUpper Key = "H"
KeyIUpper Key = "I"
KeyJUpper Key = "J"
KeyKUpper Key = "K"
KeyLUpper Key = "L"
KeyMUpper Key = "M"
KeyNUpper Key = "N"
KeyOUpper Key = "O"
KeyPUpper Key = "P"
KeyQUpper Key = "Q"
KeyRUpper Key = "R"
KeySUpper Key = "S"
KeyTUpper Key = "T"
KeyUUpper Key = "U"
KeyVUpper Key = "V"
KeyWUpper Key = "W"
KeyXUpper Key = "X"
KeyYUpper Key = "Y"
KeyZUpper Key = "Z"
Key0 Key = "0"
Key1 Key = "1"
Key2 Key = "2"
Key3 Key = "3"
Key4 Key = "4"
Key5 Key = "5"
Key6 Key = "6"
Key7 Key = "7"
Key8 Key = "8"
Key9 Key = "9"
KeyHyphen Key = "-"
KeySpace Key = " "
KeyExclamationMark Key = "!"
KeyAtSign Key = "@"
KeyHash Key = "#"
KeyDollar Key = "$"
KeyPercent Key = "%"
KeyCaret Key = "^"
KeyAmpersand Key = "&"
KeyAsterisk Key = "*"
KeyDot Key = "."
KeyComma Key = ","
KeySlash Key = "/"
KeyBackslash Key = "\\"
KeyQuestionMark Key = "?"
KeySemicolon Key = ";"
KeyColon Key = ":"
KeyApostrophe Key = "'"
KeyTab Key = "\t"
)
type LayoutRegistry struct {
layouts map[string]Layout
}
func NewLayoutRegistry() *LayoutRegistry {
return &LayoutRegistry{
layouts: make(map[string]Layout),
}
}
var DefaultLayoutRegistry = NewLayoutRegistry()
func (r *LayoutRegistry) Register(name string, layout Layout) {
r.layouts[name] = layout
}
func TypeString(text string, layout string) error {
if layout == "" {
layout = "qwerty"
}
if _, ok := DefaultLayoutRegistry.layouts[layout]; !ok {
return errors.New("layout not found")
}
keyboard, err := uinput.CreateKeyboard("/dev/uinput", []byte("testkeyboard"))
if err != nil {
return err
}
for _, c := range text {
key := Key(string(c))
err := DefaultLayoutRegistry.layouts[layout].TypeKey(key, keyboard)
if err != nil {
fmt.Println(err)
}
time.Sleep(10 * time.Millisecond)
}
err = keyboard.Close()
if err != nil {
return err
}
return nil
}
func Paste(layout string) error {
if layout == "" {
layout = "qwerty"
}
if _, ok := DefaultLayoutRegistry.layouts[layout]; !ok {
return errors.New("layout not found")
}
keyboard, err := uinput.CreateKeyboard("/dev/uinput", []byte("Goldwarden Autotype"))
if err != nil {
return err
}
keyboard.KeyDown(uinput.KeyLeftctrl)
time.Sleep(100 * time.Millisecond)
DefaultLayoutRegistry.layouts[layout].TypeKey(KeyV, keyboard)
time.Sleep(100 * time.Millisecond)
keyboard.KeyUp(uinput.KeyLeftctrl)
return nil
}
func Sleep() {
time.Sleep(20 * time.Millisecond)
}

View file

@ -1,9 +0,0 @@
//go:build linux && uinput
package autotype
import "github.com/quexten/goldwarden/autofill/autotype/uinput"
func TypeString(text string, layout string) error {
return uinput.TypeString(text, layout)
}

View file

@ -1,7 +0,0 @@
//go:build !linux
package autotype
func TypeString(text string, layout string) error {
return errors.New("Not implemented")
}

View file

@ -1,336 +0,0 @@
//go:build !noautofill
package autofill
import (
"fmt"
"image"
"image/color"
"log"
"math"
"os"
"os/user"
"sort"
"strings"
"gioui.org/app"
"gioui.org/font/gofont"
"gioui.org/io/key"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"github.com/shirou/gopsutil/v3/process"
)
type AutofillEntry struct {
Username string
Name string
UUID string
}
var autofillEntries = []AutofillEntry{}
var onAutofill func(string, chan bool)
var selectedEntry = 0
type scoredAutofillEntry struct {
autofillEntry AutofillEntry
score int
}
func getUserProcs() []string {
procNames := []string{}
procs, err := process.Processes()
if err != nil {
return []string{}
}
for _, proc := range procs {
user, err := user.Current()
if err != nil {
continue
}
procuser, err := proc.Username()
if err != nil {
continue
}
if procuser == user.Username {
procName, err := proc.Name()
if err != nil {
continue
}
procNames = append(procNames, strings.ToLower(procName))
}
}
return procNames
}
func GetFilteredAutofillEntries(entries []AutofillEntry, filter string) []AutofillEntry {
if len(filter) == 0 {
return []AutofillEntry{}
}
filter = strings.ToLower(filter)
// filter entrien by whether they contain the filter string
filteredEntries := []AutofillEntry{}
for _, entry := range entries {
name := strings.ToLower(entry.Name)
if strings.HasPrefix(name, filter) {
filteredEntries = append(filteredEntries, entry)
}
}
processes := getUserProcs()
scoredEntries := []scoredAutofillEntry{}
for _, entry := range filteredEntries {
score := 0
name := strings.ToLower(entry.Name)
filter = strings.ToLower(filter)
maxProcessScore := 0
for _, process := range processes {
score := 0
sharedPrefixLen := 0
for i := 0; i < len(process) && i < len(entry.Name); i++ {
if process[i] == name[i] {
sharedPrefixLen++
} else {
break
}
}
sharedPrefixLenPercent := float32(sharedPrefixLen) / float32(math.Min(float64(len(process)), float64(len(name))))
if sharedPrefixLen > 0 {
score += int(sharedPrefixLenPercent * 7)
}
if score > maxProcessScore {
maxProcessScore = score
}
}
score += maxProcessScore
scoredEntries = append(scoredEntries, scoredAutofillEntry{entry, score})
}
sort.Slice(scoredEntries, func(i, j int) bool {
return scoredEntries[i].score > scoredEntries[j].score
})
var filteredEntries1 []AutofillEntry
for _, scoredEntry := range scoredEntries {
if scoredEntry.score == 0 {
continue
}
filteredEntries1 = append(filteredEntries1, scoredEntry.autofillEntry)
}
return filteredEntries1
}
func RunAutofill(entries []AutofillEntry, onAutofillFunc func(string, chan bool)) {
autofillEntries = entries
onAutofill = onAutofillFunc
go func() {
w := app.NewWindow()
w.Option(app.Size(unit.Dp(600), unit.Dp(800)))
w.Option(app.Decorated(false))
w.Perform(system.ActionCenter)
w.Perform(system.ActionRaise)
lineEditor.Focus()
if err := loop(w); err != nil {
log.Fatal(err)
}
}()
app.Main()
}
var lineEditor = &widget.Editor{
SingleLine: true,
Submit: true,
}
var (
unselected = color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xFF}
unselectedText = color.NRGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF}
background = color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xFF}
selected = color.NRGBA{R: 0x65, G: 0x1F, B: 0xFF, A: 0xFF}
selectedText = color.NRGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF}
)
var th = material.NewTheme(gofont.Collection())
var list = layout.List{Axis: layout.Vertical}
func doLayout(gtx layout.Context) layout.Dimensions {
var filteredEntries []AutofillEntry = GetFilteredAutofillEntries(autofillEntries, lineEditor.Text())
if selectedEntry >= 10 || selectedEntry >= len(filteredEntries) {
selectedEntry = 0
}
return Background{Color: background, CornerRadius: unit.Dp(0)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return Background{Color: background, CornerRadius: unit.Dp(0)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(10)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
searchBox := material.Editor(th, lineEditor, "Search query")
searchBox.Color = selectedText
border := widget.Border{Color: selectedText, CornerRadius: unit.Dp(8), Width: unit.Dp(2)}
return border.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(8)).Layout(gtx, searchBox.Layout)
})
})
})
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(10)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return list.Layout(gtx, len(filteredEntries), func(gtx layout.Context, i int) layout.Dimensions {
entry := filteredEntries[i]
return layout.Inset{Bottom: unit.Dp(10)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
isSelected := i == selectedEntry
var color color.NRGBA
if isSelected {
color = selected
} else {
color = unselected
}
return Background{Color: color, CornerRadius: unit.Dp(8)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(10)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
dimens := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
t := material.H6(th, entry.Name)
if isSelected {
t.Color = selectedText
} else {
t.Color = unselectedText
}
return t.Layout(gtx)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
t := material.Body1(th, entry.Username)
if isSelected {
t.Color = selectedText
} else {
t.Color = unselectedText
}
return t.Layout(gtx)
}),
)
return dimens
})
})
})
})
})
}))
})
}
func loop(w *app.Window) error {
var ops op.Ops
for {
e := <-w.Events()
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
key.InputOp{
Keys: key.Set(key.NameReturn + "|" + key.NameEscape + "|" + key.NameDownArrow),
Tag: 0,
}.Add(gtx.Ops)
t := lineEditor.Events()
for _, ev := range t {
switch ev.(type) {
case widget.SubmitEvent:
entries := GetFilteredAutofillEntries(autofillEntries, lineEditor.Text())
if len(entries) == 0 {
fmt.Println("no entries")
continue
} else {
w.Perform(system.ActionMinimize)
c := make(chan bool)
go onAutofill(entries[selectedEntry].UUID, c)
go func() {
<-c
os.Exit(0)
}()
}
}
}
test := gtx.Events(0)
for _, ev := range test {
switch ev := ev.(type) {
case key.Event:
switch ev.Name {
case key.NameReturn:
fmt.Println("uncaught submit")
return nil
case key.NameDownArrow:
if ev.State == key.Press {
selectedEntry++
if selectedEntry >= 10 {
selectedEntry = 0
}
}
case key.NameEscape:
os.Exit(0)
}
}
}
doLayout(gtx)
e.Frame(gtx.Ops)
}
}
}
type Background struct {
Color color.NRGBA
CornerRadius unit.Dp
}
func (b Background) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
m := op.Record(gtx.Ops)
dims := w(gtx)
size := dims.Size
call := m.Stop()
if r := gtx.Dp(b.CornerRadius); r > 0 {
defer clip.RRect{
Rect: image.Rect(0, 0, size.X, size.Y),
NE: r, NW: r, SE: r, SW: r,
}.Push(gtx.Ops).Pop()
}
fill{b.Color}.Layout(gtx, size)
call.Add(gtx.Ops)
return dims
}
type fill struct {
col color.NRGBA
}
func (f fill) Layout(gtx layout.Context, sz image.Point) layout.Dimensions {
defer clip.Rect(image.Rectangle{Max: sz}).Push(gtx.Ops).Pop()
paint.ColorOp{Color: f.col}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
return layout.Dimensions{Size: sz}
}

View file

@ -1,4 +1,4 @@
//go:build linux && !uinput
//go:build linux
package autotype
@ -13,7 +13,7 @@ var globalID = 0
const autoTypeDelay = 1 * time.Millisecond
func TypeString(textToType string, layout string) {
func TypeString(textToType string) {
bus, err := dbus.SessionBus()
if err != nil {
panic(err)

View file

@ -52,7 +52,7 @@ func readMessageLength(msg []byte) int {
func send(msg SendMessage) {
byteMsg := dataToBytes(msg)
logging.Debugf("Sending message: " + string(byteMsg))
logging.Debugf("[SENSITIVE] Sending message: " + string(byteMsg))
writeMessageLength(byteMsg)
var msgBuf bytes.Buffer

View file

@ -0,0 +1,32 @@
//go:build !nooplogging
package logging
import (
"fmt"
"os"
"time"
)
func writeLog(level string, format string, args ...interface{}) {
file, _ := os.OpenFile("/tmp/DELETE_ME.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
// log with date and time
file.WriteString("[" + level + "] ")
file.WriteString(time.Now().Format("2006-01-02 15:04:05") + " ")
file.WriteString(fmt.Sprintf(format, args...))
file.WriteString("\n")
file.Close()
}
func Debugf(format string, args ...interface{}) {
writeLog("DEBUG", format, args...)
}
func Errorf(format string, args ...interface{}) {
writeLog("ERROR", format, args...)
}
func Panicf(format string, args ...interface{}) {
writeLog("PANIC", format, args...)
panic("")
}

View file

@ -1,4 +1,4 @@
//go:build !logging
//go:build nooplogging
package logging

View file

@ -5,6 +5,8 @@ import (
"os"
"path/filepath"
"strings"
"github.com/quexten/goldwarden/browserbiometrics/logging"
)
const appID = "com.quexten.bw-bio-handler"
@ -25,7 +27,9 @@ func Main() {
return
}
logging.Debugf("Starting browserbiometrics")
transportKey = generateTransportKey()
logging.Debugf("Generated transport key")
setupCommunication()
readLoop()

View file

@ -18,11 +18,13 @@ func readLoop() {
lengthBytes := make([]byte, 4)
lengthNum := int(0)
logging.Debugf("Sending connected message")
send(SendMessage{
Command: "connected",
AppID: appID,
})
logging.Debugf("Starting read loop")
for b, err := s.Read(lengthBytes); b > 0 && err == nil; b, err = s.Read(lengthBytes) {
lengthNum = readMessageLength(lengthBytes)

View file

@ -1,24 +1,27 @@
//go:build !noautofill
//go:build linux
package cmd
import (
"github.com/quexten/goldwarden/autofill"
"os"
"github.com/quexten/goldwarden/autotype"
"github.com/spf13/cobra"
)
var autofillCmd = &cobra.Command{
Use: "autofill",
Short: "Autofill credentials",
Long: `Autofill credentials`,
Use: "autotype",
Short: "Autotype credentials",
Long: `Autotype credentials`,
Run: func(cmd *cobra.Command, args []string) {
layout := cmd.Flag("layout").Value.String()
autofill.Run(layout, commandClient)
username, _ := cmd.Flags().GetString("username")
// get pasword from env
password := os.Getenv("PASSWORD")
autotype.TypeString(username + "\t" + password)
},
}
func init() {
rootCmd.AddCommand(autofillCmd)
autofillCmd.PersistentFlags().String("layout", "qwerty", "")
autofillCmd.PersistentFlags().Bool("use-copy-paste", false, "")
autofillCmd.PersistentFlags().String("username", "", "")
}

View file

@ -1,8 +1,13 @@
package cmd
import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/icza/gox/stringsx"
"github.com/quexten/goldwarden/client"
"github.com/quexten/goldwarden/ipc/messages"
"github.com/spf13/cobra"
)
@ -54,6 +59,61 @@ var getLoginCmd = &cobra.Command{
},
}
var listLoginsCmd = &cobra.Command{
Use: "list",
Short: "Lists all logins in your vault",
Long: `Lists all logins in your vault.`,
Run: func(cmd *cobra.Command, args []string) {
loginIfRequired()
logins, err := ListLogins(commandClient)
if err != nil {
handleSendToAgentError(err)
return
}
fmt.Println("[")
for index, 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, "\"", "\\\"")),
}
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()
}
}
fmt.Println("]")
},
}
func ListLogins(client client.Client) ([]messages.DecryptedLoginCipher, error) {
resp, err := client.SendToAgent(messages.ListLoginsRequest{})
if err != nil {
return []messages.DecryptedLoginCipher{}, err
}
switch resp.(type) {
case messages.GetLoginsResponse:
castedResponse := (resp.(messages.GetLoginsResponse))
return castedResponse.Result, nil
case messages.ActionResponse:
castedResponse := (resp.(messages.ActionResponse))
return []messages.DecryptedLoginCipher{}, errors.New("Error: " + castedResponse.Message)
default:
return []messages.DecryptedLoginCipher{}, errors.New("Wrong response type")
}
}
func init() {
rootCmd.AddCommand(baseLoginCmd)
baseLoginCmd.AddCommand(getLoginCmd)
@ -61,4 +121,5 @@ func init() {
getLoginCmd.PersistentFlags().String("username", "", "")
getLoginCmd.PersistentFlags().String("uuid", "", "")
getLoginCmd.PersistentFlags().Bool("full", false, "")
baseLoginCmd.AddCommand(listLoginsCmd)
}

View file

@ -1,6 +1,8 @@
package cmd
import (
"fmt"
"github.com/quexten/goldwarden/ipc/messages"
"github.com/spf13/cobra"
)
@ -89,9 +91,37 @@ var purgeCmd = &cobra.Command{
},
}
var statusCmd = &cobra.Command{
Use: "status",
Short: "Shows the vault status",
Long: `Shows the vault status.`,
Run: func(cmd *cobra.Command, args []string) {
request := messages.VaultStatusRequest{}
result, err := commandClient.SendToAgent(request)
if err != nil {
handleSendToAgentError(err)
return
}
switch result.(type) {
case messages.VaultStatusResponse:
status := result.(messages.VaultStatusResponse)
fmt.Println("{")
fmt.Println(" \"locked\":", status.Locked, ",")
fmt.Println(" \"loginEntries\":", status.NumberOfLogins, ",")
fmt.Println(" \"noteEntries\":", status.NumberOfNotes)
fmt.Println("}")
default:
println("Wrong response type")
}
},
}
func init() {
rootCmd.AddCommand(vaultCmd)
vaultCmd.AddCommand(unlockCmd)
vaultCmd.AddCommand(lockCmd)
vaultCmd.AddCommand(purgeCmd)
vaultCmd.AddCommand(statusCmd)
}

View file

@ -0,0 +1,31 @@
id: com.quexten.Goldwarden
runtime: org.gnome.Platform
runtime-version: '45'
sdk: org.gnome.Sdk
command: main.py
finish-args:
- --share=ipc
- --share=network
- --socket=wayland
- --socket=fallback-x11
- --device=dri
# - --filesystem=home
- --filesystem=host
# polkit & locking.. should filter this later on
- --socket=session-bus
- --socket=system-bus
modules:
- name: goldwarden
buildsystem: simple
build-options:
build-args:
- "--share=network"
build-commands:
- pip3 install --prefix=/app tendo
- install -D main.py /app/bin/main.py
- install -D com.quexten.Goldwarden.desktop /app/share/applications/com.quexten.Goldwarden.desktop
- install -D goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg
- cp -R ./ /app/bin/
sources:
- type: dir
path: ui

47
go.mod
View file

@ -7,22 +7,21 @@ require (
github.com/LlamaNite/llamalog v0.2.1
github.com/amenzhinsky/go-polkit v0.0.0-20210519083301-ee6a51849123
github.com/atotto/clipboard v0.1.4
github.com/awnumar/memguard v0.22.3
github.com/bendahl/uinput v1.6.2
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/awnumar/memguard v0.22.4
github.com/bendahl/uinput v1.7.0
github.com/google/uuid v1.5.0
github.com/gorilla/websocket v1.5.1
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/shirou/gopsutil/v3 v3.23.6
github.com/spf13/cobra v1.7.0
github.com/tink-crypto/tink-go/v2 v2.0.0
github.com/twpayne/go-pinentry v0.2.0
github.com/vmihailenco/msgpack/v5 v5.3.5
golang.org/x/crypto v0.13.0
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
golang.org/x/sys v0.12.0
github.com/shirou/gopsutil/v3 v3.23.11
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
)
require (
@ -31,28 +30,32 @@ require (
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-text/typesetting v0.0.0-20230602202114-9797aefac433 // indirect
github.com/icza/gox v0.0.0-20230924165045-adcb03233bb5 // indirect
github.com/k0kubun/pp v2.4.0+incompatible // indirect
github.com/k0kubun/pp/v3 v3.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect
golang.org/x/image v0.5.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/exp/shiny v0.0.0-20231219180239-dc181d75b848 // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
require (
github.com/awnumar/memcall v0.1.2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
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/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/zerolog v1.29.1
github.com/rs/zerolog v1.31.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
)

137
go.sum
View file

@ -8,20 +8,22 @@ gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
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/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=
github.com/amenzhinsky/go-polkit v0.0.0-20210519083301-ee6a51849123/go.mod h1:CdMR3dsiNi5M2BbtFlMo85mRbNt6LiMw04UBzJmoVEU=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/awnumar/memcall v0.1.2 h1:7gOfDTL+BJ6nnbtAp9+HQzUFjtP1hEseRQq8eP055QY=
github.com/awnumar/memcall v0.1.2/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo=
github.com/awnumar/memguard v0.22.3 h1:b4sgUXtbUjhrGELPbuC62wU+BsPQy+8lkWed9Z+pj0Y=
github.com/awnumar/memguard v0.22.3/go.mod h1:mmGunnffnLHlxE5rRgQc3j+uwPZ27eYb61ccr8Clz2Y=
github.com/bendahl/uinput v1.6.2 h1:tIz52QyKDx1i1nObUkts3AZa/bULfLhPA5a+xKGlRPI=
github.com/bendahl/uinput v1.6.2/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8=
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/bendahl/uinput v1.7.0 h1:nA4fm8Wu8UYNOPykIZm66nkWEyvxzfmJ8YC02PM40jg=
github.com/bendahl/uinput v1.7.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8=
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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -35,27 +37,34 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/icza/gox v0.0.0-20230924165045-adcb03233bb5 h1:K7KEFpKgVcjj98jOu2Z3xMBTtTwfYVT90Zmo3ZuWmbE=
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/k0kubun/pp v2.4.0+incompatible h1:M9iQzcejGfiBjDa7+Tc0rJgR7WFKP6rim/Q0DDrAT3g=
github.com/k0kubun/pp v2.4.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs=
github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA=
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/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/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
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=
@ -68,18 +77,18 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
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/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
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=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v3 v3.23.6 h1:5y46WPI9QBKBbK7EEccUPNXpJpNrvPuTD0O2zHEHT08=
github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU=
github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
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=
@ -87,77 +96,53 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tink-crypto/tink-go/v2 v2.0.0 h1:LutFJapahsM0i/6hKfOkzSYTVeshmFs+jloZXqe9z9s=
github.com/tink-crypto/tink-go/v2 v2.0.0/go.mod h1:QAbyq9LZncomYnScxlfaHImbV4ieNIe6bnu/Xcqqox4=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/twpayne/go-pinentry v0.2.0 h1:hS5NEJiilop9xP9pBX/1NYduzDlGGMdg1KamTBTrOWw=
github.com/twpayne/go-pinentry v0.2.0/go.mod h1:r6buhMwARxnnL0VRBqfd1tE6Fadk1kfP00GRMutEspY=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
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/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
github.com/twpayne/go-pinentry v0.3.0 h1:Rr+fEOZXmeItOb4thjeVaBWJKB9Xa/eojolycyF/26c=
github.com/twpayne/go-pinentry v0.3.0/go.mod h1:iOIZD+9np/2V24OdCGos7Y1/xX90wc6VEAZsgb+r9D4=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/exp/shiny v0.0.0-20231219180239-dc181d75b848 h1:LnDWUUS+bxOesHc+QBvFFmS4v0ZH+Vtg0EncMANwN9Q=
golang.org/x/exp/shiny v0.0.0-20231219180239-dc181d75b848/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -17,6 +17,17 @@ type WipeVaultRequest struct {
type GetVaultPINRequest struct {
}
type VaultStatusRequest struct {
}
type VaultStatusResponse struct {
Locked bool
NumberOfLogins int
NumberOfNotes int
// todo websocket status
// todo last synced
}
func init() {
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req LockVaultRequest
@ -62,4 +73,22 @@ func init() {
}
return req, nil
}, GetVaultPINRequest{})
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req VaultStatusRequest
err := json.Unmarshal(payload, &req)
if err != nil {
panic("Unmarshal: " + err.Error())
}
return req, nil
}, VaultStatusRequest{})
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req VaultStatusResponse
err := json.Unmarshal(payload, &req)
if err != nil {
panic("Unmarshal: " + err.Error())
}
return req, nil
}, VaultStatusResponse{})
}

View file

@ -38,6 +38,7 @@ func main() {
User: os.Getenv("GOLDWARDEN_AUTH_USER"),
Password: os.Getenv("GOLDWARDEN_AUTH_PASSWORD"),
Pin: os.Getenv("GOLDWARDEN_PIN"),
UseMemguard: os.Getenv("GOLDWARDEN_NO_MEMGUARD") != "true",
ConfigDirectory: configPath,
}

108
ui/autofill.py Normal file
View file

@ -0,0 +1,108 @@
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
import gc
import time
from gi.repository import Gtk, Adw, GLib
import goldwarden
import clipboard
from threading import Thread
import sys
import os
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:
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)
GLib.idle_add(lambda: self.show())
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)
elif keyval == 117:
print("copy username")
clipboard.write(self.history_list.get_selected_row().username)
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")
app.run(sys.argv)

101
ui/autotype/autotype.py Normal file
View file

@ -0,0 +1,101 @@
# TODO??!?!? for now using golang implementation
import dbus
import dbus.mainloop.glib
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib
import random
import time
step = 0
def typestring(text):
step = 0
handle = ""
def handler(*args, **kwargs):
global step
if step == 0:
handle_xdp_session_created(*args, **kwargs)
elif step == 1:
handle_xdp_devices_selected(*args, **kwargs)
elif step == 2:
handle_session_start(*args, **kwargs)
else:
print(args, kwargs)
step += 1
def handle_session_start(code, results, object_path):
global handle
if code != 0:
raise Exception("Could not start session")
for sym in text:
if sym == "\t":
inter.NotifyKeyboardKeycode(handle, {}, 15, 1)
time.sleep(0.001)
inter.NotifyKeyboardKeycode(handle, {}, 15, 0)
time.sleep(0.001)
else:
inter.NotifyKeyboardKeysym(handle, {}, ord(sym), 1)
time.sleep(0.001)
inter.NotifyKeyboardKeysym(handle, {}, ord(sym), 0)
time.sleep(0.001)
bus
def handle_xdp_devices_selected(code, results, object_path):
global handle
if code != 0:
raise Exception("Could not select devices")
start_options = {
"handle_token": "krfb" + str(random.randint(0, 999999999))
}
reply = inter.Start(handle, "", start_options)
print(reply)
def handle_xdp_session_created(code, results, object_path):
global handle
if code != 0:
raise Exception("Could not create session")
print(results)
handle = results["session_handle"]
# select sources for the session
selection_options = {
"types": dbus.UInt32(7), # request all (KeyBoard, Pointer, TouchScreen)
"handle_token": "krfb" + str(random.randint(0, 999999999))
}
selector_reply = inter.SelectDevices(handle, selection_options)
print(selector_reply)
def main():
global bus
global inter
loop = GLib.MainLoop()
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
obj = bus.get_object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop")
inter = dbus.Interface(obj, "org.freedesktop.portal.RemoteDesktop")
bus.add_signal_receiver(
handler,
signal_name="Response",
dbus_interface="org.freedesktop.portal.Request",
bus_name="org.freedesktop.portal.Desktop",
path_keyword="object_path")
print(inter)
result = inter.CreateSession({
"session_handle_token": "sessionhandletoken"
})
print(result)
loop.run()
main()

58
ui/background.py Normal file
View file

@ -0,0 +1,58 @@
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
import gc
from gi.repository import Gtk, Adw, GLib, Gio
from random import randint
import time
def receive_autostart(self, *args):
print("autostart enabled..!?")
print(args)
def request_autostart():
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
proxy = Gio.DBusProxy.new_sync(
bus,
Gio.DBusProxyFlags.NONE,
None,
'org.freedesktop.portal.Desktop',
'/org/freedesktop/portal/desktop',
'org.freedesktop.portal.Background',
None,
)
token = 0 + randint(10000000, 20000000)
options = {
'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']),
'dbus-activatable': GLib.Variant('b', False),
}
try:
request = proxy.RequestBackground('(sa{sv})', "", options)
if request is None:
raise Exception(
"Registering with background portal failed."
)
bus.signal_subscribe(
'org.freedesktop.portal.Desktop',
'org.freedesktop.portal.Request',
'Response',
request,
None,
Gio.DBusSignalFlags.NO_MATCH_RULE,
receive_autostart,
None,
)
except Exception as e:
print(e)
request_autostart()
loop = GLib.MainLoop()
loop.run()

5
ui/clipboard.py Normal file
View file

@ -0,0 +1,5 @@
import subprocess
def write(text):
process = subprocess.Popen(["/bin/sh", "-c", "wl-copy"], stdin=subprocess.PIPE)
process.communicate(text.encode('utf-8'))

View file

@ -0,0 +1,10 @@
[Desktop Entry]
Name=Goldwarden
Comment=A Bitwarden compatible desktop password manager
Keywords=backup;recovery;
Exec=main.py
Terminal=false
Type=Application
Icon=com.quexten.Goldwarden.svg
StartupNotify=true
Categories=GNOME;GTK;Password Manager;

129
ui/goldwarden.py Normal file
View file

@ -0,0 +1,129 @@
import subprocess
import json
import os
BINARY_PATH = "/home/quexten/go/src/github.com/quexten/goldwarden/goldwarden"
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 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 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)
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

115
ui/goldwarden.svg Normal file
View file

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
sodipodi:docname="goldwarden.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="1.6792579"
inkscape:cx="142.62253"
inkscape:cy="292.09332"
inkscape:window-width="3840"
inkscape:window-height="2091"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1">
<linearGradient
id="linearGradient17"
inkscape:collect="always">
<stop
style="stop-color:#ffeb28;stop-opacity:1;"
offset="0"
id="stop17" />
<stop
style="stop-color:#ffb608;stop-opacity:1;"
offset="0.76853603"
id="stop18" />
<stop
style="stop-color:#ffa21f;stop-opacity:1;"
offset="1"
id="stop19" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient17"
id="radialGradient18"
cx="66.050179"
cy="50.758305"
fx="66.050179"
fy="50.758305"
r="45"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1205298,9.6606045e-4,-7.5884389e-4,0.88018377,-7.9225014,6.0178603)" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:url(#radialGradient18);fill-opacity:1;stroke-width:0.262037;fill-rule:nonzero"
id="rect1"
width="90"
height="90"
x="5.0169253"
y="4.8409019"
ry="12.342399" />
<g
id="g17"
transform="matrix(1.0914831,0,0,1.0914831,-10.347732,-4.5673979)">
<circle
style="fill:#ffffff;fill-opacity:1;stroke-width:0.279194"
id="path1"
cx="70"
cy="50"
r="10" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:0.200309"
id="rect2"
width="5"
height="50"
x="67.5"
y="24.926144"
ry="2.0560377" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:0.200309"
id="rect3"
width="5"
height="50"
x="-10.85097"
y="60.616943"
ry="2.0560377"
transform="rotate(-60.033259)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:0.200309"
id="rect4"
width="5"
height="50"
x="-80.843193"
y="10.529473"
ry="2.0560377"
transform="rotate(-120.06752)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

47
ui/main.py Normal file
View file

@ -0,0 +1,47 @@
#!/usr/bin/python
import time
import subprocess
from tendo import singleton
import monitors.dbus_autofill_monitor
import sys
import goldwarden
from threading import Thread
try:
subprocess.Popen(["python3", "/app/bin/background.py"], start_new_session=True)
except:
pass
is_hidden = "--hidden" in sys.argv
if not is_hidden:
try:
subprocess.Popen(["python3", "/app/bin/settings.py"], start_new_session=True)
except:
pass
try:
me = singleton.SingleInstance()
except:
exit()
def run_daemon():
# todo: do a proper check
if is_hidden:
time.sleep(20)
if not goldwarden.is_daemon_running():
goldwarden.run_daemon()
if not goldwarden.is_daemon_running():
print("daemon not running.. autostarting")
daemonThread = Thread(target=run_daemon)
daemonThread.start()
print("starting autofill monitor")
def on_autofill():
subprocess.Popen(["python3", "/app/bin/autofill.py"], start_new_session=True)
monitors.dbus_autofill_monitor.on_autofill = on_autofill
while True:
time.sleep(60)

View file

@ -0,0 +1,23 @@
#Python DBUS Test Server
#runs until the Quit() method is called via DBUS
from gi.repository import Gtk
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
from threading import Thread
on_autofill = lambda: None
class GoldwardenDBUSService(dbus.service.Object):
def __init__(self):
bus_name = dbus.service.BusName('com.quexten.goldwarden', bus=dbus.SessionBus())
dbus.service.Object.__init__(self, bus_name, '/com/quexten/goldwarden')
@dbus.service.method('com.quexten.goldwarden.Autofill')
def autofill(self):
on_autofill()
return ""
DBusGMainLoop(set_as_default=True)
service = GoldwardenDBUSService()

View file

@ -0,0 +1 @@
# todo

221
ui/settings.py Normal file
View file

@ -0,0 +1,221 @@
#!/usr/bin/python
import sys
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
import gc
from gi.repository import Gtk, Adw, GLib
import goldwarden
from threading import Thread
class SettingsWinvdow(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.preferences_page = Adw.PreferencesPage()
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("Listening at ~/.goldwarden-ssh-agent.sock")
self.preferences_group.add(self.ssh_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.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.login_row = Adw.ActionRow()
self.login_row.set_title("Vault Login Entries")
self.login_row.set_subtitle("0")
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.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.login_button = Gtk.Button()
self.login_button.set_label("Login")
self.login_button.connect("clicked", lambda button: show_login())
self.login_button.set_sensitive(False)
self.login_button.set_margin_top(10)
self.login_button.get_style_context().add_class("suggested-action")
self.action_preferences_group.add(self.login_button)
self.set_pin_button = Gtk.Button()
self.set_pin_button.set_label("Set Pin")
def set_pin():
set_pin_thread = Thread(target=goldwarden.enable_pin)
set_pin_thread.start()
self.set_pin_button.connect("clicked", lambda button: set_pin())
self.set_pin_button.set_margin_top(10)
self.set_pin_button.set_sensitive(False)
self.set_pin_button.get_style_context().add_class("suggested-action")
self.action_preferences_group.add(self.set_pin_button)
self.unlock_button = Gtk.Button()
self.unlock_button.set_label("Unlock")
self.unlock_button.set_margin_top(10)
def unlock_button_clicked():
action = goldwarden.unlock if self.unlock_button.get_label() == "Unlock" else goldwarden.lock
unlock_thread = Thread(target=action)
unlock_thread.start()
self.unlock_button.connect("clicked", lambda button: unlock_button_clicked())
# set disabled
self.unlock_button.set_sensitive(False)
self.action_preferences_group.add(self.unlock_button)
self.logout_button = Gtk.Button()
self.logout_button.set_label("Logout")
self.logout_button.set_margin_top(10)
self.logout_button.connect("clicked", lambda button: goldwarden.purge())
self.logout_button.get_style_context().add_class("destructive-action")
self.action_preferences_group.add(self.logout_button)
def update_labels():
pin_set = goldwarden.is_pin_enabled()
status = goldwarden.get_vault_status()
if status != None:
locked = status["locked"]
self.login_button.set_sensitive(pin_set and not locked)
self.set_pin_button.set_sensitive(not pin_set or not locked)
self.status_row.set_subtitle(str("Unlocked" if not locked else "Locked"))
self.login_row.set_subtitle(str(status["loginEntries"]))
self.notes_row.set_subtitle(str(status["noteEntries"]))
self.unlock_button.set_sensitive(True)
self.unlock_button.set_label("Unlock" if locked else "Lock")
else:
is_daemon_running = goldwarden.is_daemon_running()
if not is_daemon_running:
self.status_row.set_subtitle("Daemon not running")
GLib.timeout_add(1000, 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)
self.connect('activate', self.on_activate)
def on_activate(self, app):
self.settings_win = SettingsWinvdow(application=app)
self.settings_win.present()
app = MyApp(application_id="com.quexten.Goldwarden")
def show_login():
dialog = Gtk.Dialog(title="Goldwarden")
preference_group = Adw.PreferencesGroup()
preference_group.set_title("Config")
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://api.bitwarden.com/")
preference_group.add(api_url_entry)
identity_url_entry = Adw.EntryRow()
identity_url_entry.set_title("Identity Url")
identity_url_entry.set_text("https://identity.bitwarden.com/")
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")
dialog.get_content_area().append(auth_preference_group)
email_entry = Adw.EntryRow()
email_entry.set_title("Email")
email_entry.set_text("")
auth_preference_group.add(email_entry)
dialog.add_button("Login", Gtk.ResponseType.OK)
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())
def login():
res = goldwarden.login_with_password(email_entry.get_text(), "password")
def handle_res():
print("handle res", res)
if res == "ok":
dialog.close()
print("ok")
elif res == "badpass":
bad_pass_diag = Gtk.MessageDialog(transient_for=dialog, modal=True, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text="Bad password")
bad_pass_diag.connect("response", lambda dialog, response: bad_pass_diag.close())
bad_pass_diag.present()
GLib.idle_add(handle_res)
login_thread = Thread(target=login)
login_thread.start()
#ok response
dialog.connect("response", lambda dialog, response: on_save(response))
dialog.set_default_size(400, 200)
dialog.set_modal(True)
dialog.present()
app.run(sys.argv)