diff --git a/.gitignore b/.gitignore index 26bab9a..53cdaf5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode -*debug* -goldwarden* \ No newline at end of file +goldwarden +__pycache__ +.flatpak-builder \ No newline at end of file diff --git a/Readme.md b/Readme.md index 9b821e4..1ec2790 100644 --- a/Readme.md +++ b/Readme.md @@ -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 -- ``` -Autofill +Autofill (Flatpak Only) ``` -goldwarden autofill --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. diff --git a/agent/actions/actions.go b/agent/actions/actions.go index 961e30c..7fa2ab8 100644 --- a/agent/actions/actions.go +++ b/agent/actions/actions.go @@ -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 { diff --git a/agent/actions/browserbiometrics.go b/agent/actions/browserbiometrics.go index 27995e9..5f4e4d3 100644 --- a/agent/actions/browserbiometrics.go +++ b/agent/actions/browserbiometrics.go @@ -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 } diff --git a/agent/actions/login.go b/agent/actions/login.go index a751a15..dbe50de 100644 --- a/agent/actions/login.go +++ b/agent/actions/login.go @@ -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, diff --git a/agent/actions/logins.go b/agent/actions/logins.go index 0c6b52b..a53a662 100644 --- a/agent/actions/logins.go +++ b/agent/actions/logins.go @@ -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 diff --git a/agent/actions/vault.go b/agent/actions/vault.go index a035f68..56d12c2 100644 --- a/agent/actions/vault.go +++ b/agent/actions/vault.go @@ -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) } diff --git a/agent/bitwarden/auth.go b/agent/bitwarden/auth.go index 15f3617..a6e614a 100644 --- a/agent/bitwarden/auth.go +++ b/agent/bitwarden/auth.go @@ -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 } diff --git a/agent/bitwarden/crypto/crypto.go b/agent/bitwarden/crypto/crypto.go index 42437e5..e1f37e6 100644 --- a/agent/bitwarden/crypto/crypto.go +++ b/agent/bitwarden/crypto/crypto.go @@ -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) diff --git a/agent/bitwarden/crypto/encstring.go b/agent/bitwarden/crypto/encstring.go index b25cb1c..1eca8d7 100644 --- a/agent/bitwarden/crypto/encstring.go +++ b/agent/bitwarden/crypto/encstring.go @@ -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 } diff --git a/agent/bitwarden/crypto/kdf.go b/agent/bitwarden/crypto/kdf.go index 10c4bad..a368a59 100644 --- a/agent/bitwarden/crypto/kdf.go +++ b/agent/bitwarden/crypto/kdf.go @@ -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 } diff --git a/agent/bitwarden/crypto/keyhierarchy.go b/agent/bitwarden/crypto/keyhierarchy.go index 170fbcf..d0dc398 100644 --- a/agent/bitwarden/crypto/keyhierarchy.go +++ b/agent/bitwarden/crypto/keyhierarchy.go @@ -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 + } } diff --git a/agent/bitwarden/crypto/keyring.go b/agent/bitwarden/crypto/keyring.go index 34aae83..58acd33 100644 --- a/agent/bitwarden/crypto/keyring.go +++ b/agent/bitwarden/crypto/keyring.go @@ -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") } diff --git a/agent/bitwarden/models/models.go b/agent/bitwarden/models/models.go index 231d23e..5e7075e 100644 --- a/agent/bitwarden/models/models.go +++ b/agent/bitwarden/models/models.go @@ -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 } diff --git a/agent/bitwarden/passwordless.go b/agent/bitwarden/passwordless.go index 40c7062..5029211 100644 --- a/agent/bitwarden/passwordless.go +++ b/agent/bitwarden/passwordless.go @@ -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 { diff --git a/agent/bitwarden/sync.go b/agent/bitwarden/sync.go index 17cc978..33da748 100644 --- a/agent/bitwarden/sync.go +++ b/agent/bitwarden/sync.go @@ -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 } } diff --git a/agent/bitwarden/websocket.go b/agent/bitwarden/websocket.go index 3c2a4e7..5330a01 100644 --- a/agent/bitwarden/websocket.go +++ b/agent/bitwarden/websocket.go @@ -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) diff --git a/agent/config/config.go b/agent/config/config.go index c3e0400..d70dc14 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -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 diff --git a/agent/config/lockedbuffer.go b/agent/config/lockedbuffer.go new file mode 100644 index 0000000..bf27b9f --- /dev/null +++ b/agent/config/lockedbuffer.go @@ -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 +} diff --git a/agent/defaultmainloop.go b/agent/defaultmainloop.go index c90ef4e..ace84b0 100644 --- a/agent/defaultmainloop.go +++ b/agent/defaultmainloop.go @@ -1,4 +1,4 @@ -//go:build !windows && !darwin && !linux +//go:build (!windows && !darwin && !linux) || noautofill package agent diff --git a/agent/giouimainloop.go b/agent/giouimainloop.go index ee802bc..678e024 100644 --- a/agent/giouimainloop.go +++ b/agent/giouimainloop.go @@ -1,4 +1,4 @@ -//go:build windows || darwin || linux +//go:build (windows || darwin || linux) && !noautofill package agent diff --git a/agent/sockets/callingcontext.go b/agent/sockets/callingcontext.go index 76d3063..3541094 100644 --- a/agent/sockets/callingcontext.go +++ b/agent/sockets/callingcontext.go @@ -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{ diff --git a/agent/ssh/keys.go b/agent/ssh/keys.go index f1f1c33..235a3f0 100644 --- a/agent/ssh/keys.go +++ b/agent/ssh/keys.go @@ -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, diff --git a/agent/systemauth/biometrics/polkit.go b/agent/systemauth/biometrics/polkit.go index dcb7565..7a08647 100644 --- a/agent/systemauth/biometrics/polkit.go +++ b/agent/systemauth/biometrics/polkit.go @@ -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 } diff --git a/agent/systemauth/systemauth.go b/agent/systemauth/systemauth.go index 931a5cc..78e2995 100644 --- a/agent/systemauth/systemauth.go +++ b/agent/systemauth/systemauth.go @@ -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) diff --git a/agent/unixsocketagent.go b/agent/unixsocketagent.go index 76a7303..728414c 100644 --- a/agent/unixsocketagent.go +++ b/agent/unixsocketagent.go @@ -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 { diff --git a/agent/vault/vault.go b/agent/vault/vault.go index b6dec7f..8431865 100644 --- a/agent/vault/vault.go +++ b/agent/vault/vault.go @@ -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 { diff --git a/agent/virtualagent.go b/agent/virtualagent.go index 156db4a..cd0f81b 100644 --- a/agent/virtualagent.go +++ b/agent/virtualagent.go @@ -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 { diff --git a/autofill/autofill.go b/autofill/autofill.go deleted file mode 100644 index 527ed14..0000000 --- a/autofill/autofill.go +++ /dev/null @@ -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 - }) -} diff --git a/autofill/autotype/uinput/dvorak.go b/autofill/autotype/uinput/dvorak.go deleted file mode 100644 index 7dbf68f..0000000 --- a/autofill/autotype/uinput/dvorak.go +++ /dev/null @@ -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{}) -} diff --git a/autofill/autotype/uinput/qwerty.go b/autofill/autotype/uinput/qwerty.go deleted file mode 100644 index 79cc88b..0000000 --- a/autofill/autotype/uinput/qwerty.go +++ /dev/null @@ -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{}) -} diff --git a/autofill/autotype/uinput/uinput.go b/autofill/autotype/uinput/uinput.go deleted file mode 100644 index 8e32fb5..0000000 --- a/autofill/autotype/uinput/uinput.go +++ /dev/null @@ -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) -} diff --git a/autofill/autotype/uinputautotype.go b/autofill/autotype/uinputautotype.go deleted file mode 100644 index 50bb452..0000000 --- a/autofill/autotype/uinputautotype.go +++ /dev/null @@ -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) -} diff --git a/autofill/autotype/unimplemented.go b/autofill/autotype/unimplemented.go deleted file mode 100644 index a7d8c73..0000000 --- a/autofill/autotype/unimplemented.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !linux - -package autotype - -func TypeString(text string, layout string) error { - return errors.New("Not implemented") -} diff --git a/autofill/gioAutofillDialog.go b/autofill/gioAutofillDialog.go deleted file mode 100644 index f3ce170..0000000 --- a/autofill/gioAutofillDialog.go +++ /dev/null @@ -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} -} diff --git a/autofill/autotype/libportalautotype.go b/autotype/libportalautotype.go similarity index 96% rename from autofill/autotype/libportalautotype.go rename to autotype/libportalautotype.go index 64ad3fb..fbd09b2 100644 --- a/autofill/autotype/libportalautotype.go +++ b/autotype/libportalautotype.go @@ -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) diff --git a/browserbiometrics/communication.go b/browserbiometrics/communication.go index 534cc6d..89d95b4 100644 --- a/browserbiometrics/communication.go +++ b/browserbiometrics/communication.go @@ -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 diff --git a/browserbiometrics/logging/debuglogger.go b/browserbiometrics/logging/debuglogger.go new file mode 100644 index 0000000..9b6966c --- /dev/null +++ b/browserbiometrics/logging/debuglogger.go @@ -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("") +} diff --git a/browserbiometrics/logging/nooplogger.go b/browserbiometrics/logging/nooplogger.go index a769ab2..7a5bdd7 100644 --- a/browserbiometrics/logging/nooplogger.go +++ b/browserbiometrics/logging/nooplogger.go @@ -1,4 +1,4 @@ -//go:build !logging +//go:build nooplogging package logging diff --git a/browserbiometrics/main.go b/browserbiometrics/main.go index d3b7bab..be3617e 100644 --- a/browserbiometrics/main.go +++ b/browserbiometrics/main.go @@ -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() diff --git a/browserbiometrics/protocol.go b/browserbiometrics/protocol.go index 050b16e..ecf97b0 100644 --- a/browserbiometrics/protocol.go +++ b/browserbiometrics/protocol.go @@ -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) diff --git a/cmd/autofill.go b/cmd/autofill.go index 1710720..c569e70 100644 --- a/cmd/autofill.go +++ b/cmd/autofill.go @@ -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", "", "") } diff --git a/cmd/logins.go b/cmd/logins.go index 7d4d530..4251fac 100644 --- a/cmd/logins.go +++ b/cmd/logins.go @@ -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) } diff --git a/cmd/vault.go b/cmd/vault.go index 7665a96..2668ede 100644 --- a/cmd/vault.go +++ b/cmd/vault.go @@ -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) } diff --git a/com.quexten.Goldwarden.yml b/com.quexten.Goldwarden.yml new file mode 100644 index 0000000..434dbe4 --- /dev/null +++ b/com.quexten.Goldwarden.yml @@ -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 \ No newline at end of file diff --git a/go.mod b/go.mod index 21b8c7a..a1d92ce 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index c43b618..6046d5d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/ipc/messages/vault.go b/ipc/messages/vault.go index 166a8cf..dff39a6 100644 --- a/ipc/messages/vault.go +++ b/ipc/messages/vault.go @@ -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{}) } diff --git a/main.go b/main.go index e65aa5f..495c347 100644 --- a/main.go +++ b/main.go @@ -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, } diff --git a/ui/autofill.py b/ui/autofill.py new file mode 100644 index 0000000..4836769 --- /dev/null +++ b/ui/autofill.py @@ -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) \ No newline at end of file diff --git a/ui/autotype/autotype.py b/ui/autotype/autotype.py new file mode 100644 index 0000000..2f75c12 --- /dev/null +++ b/ui/autotype/autotype.py @@ -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() diff --git a/ui/background.py b/ui/background.py new file mode 100644 index 0000000..f684b4f --- /dev/null +++ b/ui/background.py @@ -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() diff --git a/ui/clipboard.py b/ui/clipboard.py new file mode 100644 index 0000000..818da99 --- /dev/null +++ b/ui/clipboard.py @@ -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')) \ No newline at end of file diff --git a/ui/com.quexten.Goldwarden.desktop b/ui/com.quexten.Goldwarden.desktop new file mode 100644 index 0000000..6f154c5 --- /dev/null +++ b/ui/com.quexten.Goldwarden.desktop @@ -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; \ No newline at end of file diff --git a/ui/goldwarden.py b/ui/goldwarden.py new file mode 100644 index 0000000..dca8047 --- /dev/null +++ b/ui/goldwarden.py @@ -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 \ No newline at end of file diff --git a/ui/goldwarden.svg b/ui/goldwarden.svg new file mode 100644 index 0000000..c26e20f --- /dev/null +++ b/ui/goldwarden.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/main.py b/ui/main.py new file mode 100644 index 0000000..a2c6c91 --- /dev/null +++ b/ui/main.py @@ -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) \ No newline at end of file diff --git a/ui/monitors/dbus_autofill_monitor.py b/ui/monitors/dbus_autofill_monitor.py new file mode 100644 index 0000000..b32788f --- /dev/null +++ b/ui/monitors/dbus_autofill_monitor.py @@ -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() diff --git a/ui/monitors/global_shortcut_portal.py b/ui/monitors/global_shortcut_portal.py new file mode 100644 index 0000000..99f1089 --- /dev/null +++ b/ui/monitors/global_shortcut_portal.py @@ -0,0 +1 @@ +# todo \ No newline at end of file diff --git a/ui/settings.py b/ui/settings.py new file mode 100644 index 0000000..343ccab --- /dev/null +++ b/ui/settings.py @@ -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) \ No newline at end of file