commit
1a91189ffa
60 changed files with 1569 additions and 1635 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
.vscode
|
.vscode
|
||||||
*debug*
|
goldwarden
|
||||||
goldwarden*
|
__pycache__
|
||||||
|
.flatpak-builder
|
||||||
19
Readme.md
19
Readme.md
|
|
@ -24,6 +24,11 @@ On Linux, you need at least a working Polkit installation and a pinentry agent.
|
||||||
|
|
||||||
### Installation
|
### 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:
|
On Arch linux, or other distributions with access to the AUR, simply:
|
||||||
```
|
```
|
||||||
yay -S goldwarden
|
yay -S goldwarden
|
||||||
|
|
@ -85,9 +90,9 @@ Run a command with injected environment variables
|
||||||
goldwarden run -- <command>
|
goldwarden run -- <command>
|
||||||
```
|
```
|
||||||
|
|
||||||
Autofill
|
Autofill (Flatpak Only)
|
||||||
```
|
```
|
||||||
goldwarden autofill --layout <keyboard-layout>
|
dbus-send --type=method_call --dest=com.quexten.goldwarden /com/quexten/goldwarden com.quexten.goldwarden.Autofill.autofill
|
||||||
```
|
```
|
||||||
(Create a hotkey for this depending on your desktop environment)
|
(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)
|
[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).
|
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
|
#### 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.
|
this enables autotyping without having to modify permissions.
|
||||||
`goldwarden autofill`
|
`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
|
### 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
|
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.
|
to approve the device. If you want to log into goldwarden using another device, add the "--passwordless" parameter to the login command.
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,16 @@ func sync(ctx context.Context, vault *vault.Vault, cfg *config.Config) bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
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)
|
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, cfg, &protectedUserSymetricKey, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -15,28 +15,33 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleGetBiometricsKey(request messages.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (response messages.IPCMessage, err error) {
|
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)) {
|
if !(systemauth.VerifyPinSession(*ctx) || biometrics.CheckBiometrics(biometrics.BrowserBiometrics)) {
|
||||||
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
|
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
Message: "not approved",
|
Message: "not approved",
|
||||||
})
|
})
|
||||||
|
actionsLog.Info("Browser Biometrics: Biometrics not approved %v", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return messages.IPCMessage{}, err
|
return messages.IPCMessage{}, err
|
||||||
}
|
}
|
||||||
return response, nil
|
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 {
|
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{
|
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
Message: "not approved",
|
Message: "not approved",
|
||||||
})
|
})
|
||||||
|
actionsLog.Info("Browser Biometrics: Biometrics not approved %v", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return messages.IPCMessage{}, err
|
return messages.IPCMessage{}, err
|
||||||
}
|
}
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actionsLog.Info("Browser Biometrics: Approved, getting key...")
|
||||||
masterKey, err := cfg.GetMasterKey()
|
masterKey, err := cfg.GetMasterKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return messages.IPCMessage{}, err
|
return messages.IPCMessage{}, err
|
||||||
|
|
@ -45,6 +50,7 @@ func handleGetBiometricsKey(request messages.IPCMessage, cfg *config.Config, vau
|
||||||
response, err = messages.IPCMessageFromPayload(messages.GetBiometricsKeyResponse{
|
response, err = messages.IPCMessageFromPayload(messages.GetBiometricsKeyResponse{
|
||||||
Key: masterKeyB64,
|
Key: masterKeyB64,
|
||||||
})
|
})
|
||||||
|
actionsLog.Info("Browser Biometrics: Sending key...")
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,10 +89,15 @@ func handleLogin(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vault
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.SetUserSymmetricKey(vault.Keyring.AccountKey.Bytes())
|
cfg.SetUserSymmetricKey(vault.Keyring.GetAccountKey().Bytes())
|
||||||
cfg.SetMasterPasswordHash([]byte(masterpasswordHash))
|
cfg.SetMasterPasswordHash([]byte(masterpasswordHash))
|
||||||
cfg.SetMasterKey([]byte(masterKey.GetBytes()))
|
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 {
|
if err != nil {
|
||||||
var payload = messages.ActionResponse{
|
var payload = messages.ActionResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
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{
|
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
Message: "not approved",
|
Message: "not approved",
|
||||||
|
|
@ -123,6 +123,7 @@ func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, va
|
||||||
|
|
||||||
var decryptedName []byte = []byte{}
|
var decryptedName []byte = []byte{}
|
||||||
var decryptedUsername []byte = []byte{}
|
var decryptedUsername []byte = []byte{}
|
||||||
|
var decryptedPassword []byte = []byte{}
|
||||||
|
|
||||||
if !login.Name.IsNull() {
|
if !login.Name.IsNull() {
|
||||||
decryptedName, err = crypto.DecryptWith(login.Name, key)
|
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{
|
decryptedLoginCiphers = append(decryptedLoginCiphers, messages.DecryptedLoginCipher{
|
||||||
Name: string(decryptedName),
|
Name: string(decryptedName),
|
||||||
Username: string(decryptedUsername),
|
Username: string(decryptedUsername),
|
||||||
UUID: login.ID.String(),
|
UUID: login.ID.String(),
|
||||||
|
Password: string(decryptedPassword),
|
||||||
})
|
})
|
||||||
|
|
||||||
// prevent deadlock from enclaves
|
// prevent deadlock from enclaves
|
||||||
|
|
|
||||||
|
|
@ -64,10 +64,17 @@ func handleUnlockVault(request messages.IPCMessage, cfg *config.Config, vault *v
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, cfg, &safeUserSymmkey, true)
|
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, cfg, &safeUserSymmkey, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|
@ -178,10 +185,20 @@ func handlePinStatus(request messages.IPCMessage, cfg *config.Config, vault *vau
|
||||||
return
|
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() {
|
func init() {
|
||||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.UnlockVaultRequest{}), handleUnlockVault)
|
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.UnlockVaultRequest{}), handleUnlockVault)
|
||||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.LockVaultRequest{}), handleLockVault)
|
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.LockVaultRequest{}), handleLockVault)
|
||||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.WipeVaultRequest{}), handleWipeVault)
|
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.WipeVaultRequest{}), handleWipeVault)
|
||||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.UpdateVaultPINRequest{}), ensureBiometricsAuthorized(systemauth.AccessVault, handleUpdateVaultPin))
|
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.UpdateVaultPINRequest{}), ensureBiometricsAuthorized(systemauth.AccessVault, handleUpdateVaultPin))
|
||||||
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.GetVaultPINRequest{}), handlePinStatus)
|
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.GetVaultPINRequest{}), handlePinStatus)
|
||||||
|
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.VaultStatusRequest{}), handleVaultStatus)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/awnumar/memguard"
|
|
||||||
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
||||||
"github.com/quexten/goldwarden/agent/bitwarden/twofactor"
|
"github.com/quexten/goldwarden/agent/bitwarden/twofactor"
|
||||||
"github.com/quexten/goldwarden/agent/config"
|
"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
|
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 {
|
if err != nil {
|
||||||
return LoginResponseToken{}, crypto.MasterKey{}, "", err
|
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++ {
|
for i := 0; i < 25; i++ {
|
||||||
accessCode += string(alphabet[rand.Intn(len(alphabet))])
|
accessCode += string(alphabet[rand.Intn(len(alphabet))])
|
||||||
}
|
}
|
||||||
publicKey, err := crypto.GenerateAsymmetric()
|
publicKey, err := crypto.GenerateAsymmetric(vault.Keyring.IsMemguard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return LoginResponseToken{}, crypto.MasterKey{}, "", err
|
return LoginResponseToken{}, crypto.MasterKey{}, "", err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,24 +18,51 @@ import (
|
||||||
|
|
||||||
var b64enc = base64.StdEncoding.Strict()
|
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
|
encKey *memguard.Enclave
|
||||||
macKey *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
|
encKey *memguard.Enclave
|
||||||
}
|
}
|
||||||
|
|
||||||
func SymmetricEncryptionKeyFromBytes(key []byte) (SymmetricEncryptionKey, error) {
|
func MemguardSymmetricEncryptionKeyFromBytes(key []byte) (MemguardSymmetricEncryptionKey, error) {
|
||||||
if len(key) != 64 {
|
if len(key) != 64 {
|
||||||
memguard.WipeBytes(key)
|
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()
|
k1, err := key.encKey.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -50,12 +77,61 @@ func (key SymmetricEncryptionKey) Bytes() []byte {
|
||||||
return keyBytes
|
return keyBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssymmetricEncryptionKeyFromBytes(key []byte) (AsymmetricEncryptionKey, error) {
|
func (key MemorySymmetricEncryptionKey) Bytes() []byte {
|
||||||
k := memguard.NewEnclave(key)
|
keyBytes := make([]byte, 64)
|
||||||
return AsymmetricEncryptionKey{k}, nil
|
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()
|
buffer, err := key.encKey.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -69,6 +145,18 @@ func (key AsymmetricEncryptionKey) PublicBytes() []byte {
|
||||||
return publicKeyBytes
|
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 {
|
func isMacValid(message, messageMAC, key []byte) bool {
|
||||||
mac := hmac.New(sha256.New, key)
|
mac := hmac.New(sha256.New, key)
|
||||||
mac.Write(message)
|
mac.Write(message)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/awnumar/memguard"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EncString struct {
|
type EncString struct {
|
||||||
|
|
@ -121,16 +123,16 @@ func b64encode(src []byte) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecryptWith(s EncString, key SymmetricEncryptionKey) ([]byte, error) {
|
func DecryptWith(s EncString, key SymmetricEncryptionKey) ([]byte, error) {
|
||||||
encKeyData, err := key.encKey.Open()
|
encKeyData, err := key.EncryptionKeyBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
macKeyData, err := key.macKey.Open()
|
macKeyData, err := key.MacKeyBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := aes.NewCipher(encKeyData.Data())
|
block, err := aes.NewCipher(encKeyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -143,13 +145,13 @@ func DecryptWith(s EncString, key SymmetricEncryptionKey) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Type == AesCbc256_HmacSha256_B64 {
|
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")
|
return nil, fmt.Errorf("decrypt: cipher string type expects a MAC")
|
||||||
}
|
}
|
||||||
var msg []byte
|
var msg []byte
|
||||||
msg = append(msg, s.IV...)
|
msg = append(msg, s.IV...)
|
||||||
msg = append(msg, s.CT...)
|
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")
|
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) {
|
func EncryptWith(data []byte, typ EncStringType, key SymmetricEncryptionKey) (EncString, error) {
|
||||||
encKeyData, err := key.encKey.Open()
|
encKeyData, err := key.EncryptionKeyBytes()
|
||||||
if err != nil {
|
macKeyData, err := key.MacKeyBytes()
|
||||||
return EncString{}, err
|
|
||||||
}
|
|
||||||
macKeyData, err := key.macKey.Open()
|
|
||||||
if err != nil {
|
|
||||||
return EncString{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := EncString{}
|
s := EncString{}
|
||||||
switch typ {
|
switch typ {
|
||||||
|
|
@ -183,7 +179,7 @@ func EncryptWith(data []byte, typ EncStringType, key SymmetricEncryptionKey) (En
|
||||||
s.Type = typ
|
s.Type = typ
|
||||||
data = padPKCS7(data, aes.BlockSize)
|
data = padPKCS7(data, aes.BlockSize)
|
||||||
|
|
||||||
block, err := aes.NewCipher(encKeyData.Bytes())
|
block, err := aes.NewCipher(encKeyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
@ -196,13 +192,13 @@ func EncryptWith(data []byte, typ EncStringType, key SymmetricEncryptionKey) (En
|
||||||
mode.CryptBlocks(s.CT, data)
|
mode.CryptBlocks(s.CT, data)
|
||||||
|
|
||||||
if typ == AesCbc256_HmacSha256_B64 {
|
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")
|
return s, fmt.Errorf("encrypt: cipher string type expects a MAC")
|
||||||
}
|
}
|
||||||
var macMessage []byte
|
var macMessage []byte
|
||||||
macMessage = append(macMessage, s.IV...)
|
macMessage = append(macMessage, s.IV...)
|
||||||
macMessage = append(macMessage, s.CT...)
|
macMessage = append(macMessage, s.CT...)
|
||||||
mac := hmac.New(sha256.New, macKeyData.Bytes())
|
mac := hmac.New(sha256.New, macKeyData)
|
||||||
mac.Write(macMessage)
|
mac.Write(macMessage)
|
||||||
s.MAC = mac.Sum(nil)
|
s.MAC = mac.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
@ -210,27 +206,31 @@ func EncryptWith(data []byte, typ EncStringType, key SymmetricEncryptionKey) (En
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateAsymmetric() (AsymmetricEncryptionKey, error) {
|
func GenerateAsymmetric(useMemguard bool) (AsymmetricEncryptionKey, error) {
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AsymmetricEncryptionKey{}, err
|
return MemoryAsymmetricEncryptionKey{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
encKey, err := x509.MarshalPKCS8PrivateKey(key)
|
encKey, err := x509.MarshalPKCS8PrivateKey(key)
|
||||||
if err != nil {
|
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) {
|
func DecryptWithAsymmetric(s []byte, asymmetrickey AsymmetricEncryptionKey) ([]byte, error) {
|
||||||
key, err := asymmetrickey.encKey.Open()
|
key, err := asymmetrickey.PrivateBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedKey, err := x509.ParsePKCS8PrivateKey(key.Bytes())
|
parsedKey, err := x509.ParsePKCS8PrivateKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -248,12 +248,12 @@ func DecryptWithAsymmetric(s []byte, asymmetrickey AsymmetricEncryptionKey) ([]b
|
||||||
}
|
}
|
||||||
|
|
||||||
func EncryptWithAsymmetric(s []byte, asymmbetrickey AsymmetricEncryptionKey) ([]byte, error) {
|
func EncryptWithAsymmetric(s []byte, asymmbetrickey AsymmetricEncryptionKey) ([]byte, error) {
|
||||||
key, err := asymmbetrickey.encKey.Open()
|
key, err := asymmbetrickey.PrivateBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedKey, err := x509.ParsePKIXPublicKey(key.Bytes())
|
parsedKey, err := x509.ParsePKIXPublicKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,21 +42,19 @@ func (masterKey MasterKey) GetBytes() []byte {
|
||||||
return bytes.Clone(buffer.Bytes())
|
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()
|
defer debug.FreeOSMemory()
|
||||||
|
|
||||||
var key []byte
|
var key []byte
|
||||||
switch kdfConfig.Type {
|
switch kdfConfig.Type {
|
||||||
case PBKDF2:
|
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:
|
case Argon2ID:
|
||||||
var salt [32]byte = sha256.Sum256([]byte(strings.ToLower(email)))
|
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:
|
default:
|
||||||
password.Destroy()
|
|
||||||
return MasterKey{}, fmt.Errorf("unsupported KDF type %d", kdfConfig.Type)
|
return MasterKey{}, fmt.Errorf("unsupported KDF type %d", kdfConfig.Type)
|
||||||
}
|
}
|
||||||
password.Destroy()
|
|
||||||
|
|
||||||
return MasterKey{memguard.NewEnclave(key)}, nil
|
return MasterKey{memguard.NewEnclave(key)}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"golang.org/x/crypto/hkdf"
|
"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)
|
masterKey, err := DeriveMasterKey(password, email, kdfConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -23,7 +23,7 @@ func InitKeyringFromMasterKey(keyring *Keyring, accountKey EncString, accountPri
|
||||||
|
|
||||||
switch accountKey.Type {
|
switch accountKey.Type {
|
||||||
case AesCbc256_HmacSha256_B64:
|
case AesCbc256_HmacSha256_B64:
|
||||||
stretchedMasterKey, err := stretchKey(masterKey)
|
stretchedMasterKey, err := stretchKey(masterKey, keyring.IsMemguard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -36,43 +36,56 @@ func InitKeyringFromMasterKey(keyring *Keyring, accountKey EncString, accountPri
|
||||||
return fmt.Errorf("unsupported account key type: %d", accountKey.Type)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyring.AccountKey = &accountSymmetricKey
|
keyring.UnlockWithAccountKey(accountSymmetricKey)
|
||||||
|
|
||||||
pkcs8PrivateKey, err := DecryptWith(accountPrivateKey, accountSymmetricKey)
|
pkcs8PrivateKey, err := DecryptWith(accountPrivateKey, accountSymmetricKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
keyring.OrganizationKeys = orgKeys
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitKeyringFromUserSymmetricKey(keyring *Keyring, accountSymmetricKey SymmetricEncryptionKey, accountPrivateKey EncString, orgKeys map[string]string) error {
|
func InitKeyringFromUserSymmetricKey(keyring *Keyring, accountSymmetricKey SymmetricEncryptionKey, accountPrivateKey EncString, orgKeys map[string]string) error {
|
||||||
keyring.AccountKey = &accountSymmetricKey
|
keyring.UnlockWithAccountKey(accountSymmetricKey)
|
||||||
|
|
||||||
pkcs8PrivateKey, err := DecryptWith(accountPrivateKey, accountSymmetricKey)
|
pkcs8PrivateKey, err := DecryptWith(accountPrivateKey, accountSymmetricKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
keyring.OrganizationKeys = orgKeys
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func stretchKey(masterKey MasterKey) (SymmetricEncryptionKey, error) {
|
func stretchKey(masterKey MasterKey, useMemguard bool) (SymmetricEncryptionKey, error) {
|
||||||
key := make([]byte, 32)
|
key := make([]byte, 32)
|
||||||
macKey := make([]byte, 32)
|
macKey := make([]byte, 32)
|
||||||
|
|
||||||
buffer, err := masterKey.encKey.Open()
|
buffer, err := masterKey.encKey.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SymmetricEncryptionKey{}, err
|
return MemorySymmetricEncryptionKey{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var r io.Reader
|
var r io.Reader
|
||||||
|
|
@ -80,5 +93,10 @@ func stretchKey(masterKey MasterKey) (SymmetricEncryptionKey, error) {
|
||||||
r.Read(key)
|
r.Read(key)
|
||||||
r = hkdf.Expand(sha256.New, buffer.Data(), []byte("mac"))
|
r = hkdf.Expand(sha256.New, buffer.Data(), []byte("mac"))
|
||||||
r.Read(macKey)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,38 +2,66 @@ package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/quexten/goldwarden/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var keyringLog = logging.GetLogger("Goldwarden", "Keyring")
|
||||||
|
|
||||||
type Keyring struct {
|
type Keyring struct {
|
||||||
AccountKey *SymmetricEncryptionKey
|
isLocked bool
|
||||||
|
accountKey SymmetricEncryptionKey
|
||||||
AsymmetricEncyryptionKey AsymmetricEncryptionKey
|
AsymmetricEncyryptionKey AsymmetricEncryptionKey
|
||||||
|
IsMemguard bool
|
||||||
OrganizationKeys map[string]string
|
OrganizationKeys map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKeyring(accountKey *SymmetricEncryptionKey) Keyring {
|
func NewMemoryKeyring(accountKey *MemorySymmetricEncryptionKey) Keyring {
|
||||||
|
keyringLog.Info("Creating new memory keyring")
|
||||||
return 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 {
|
func (keyring Keyring) IsLocked() bool {
|
||||||
return keyring.AccountKey == nil
|
return keyring.isLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
func (keyring *Keyring) Lock() {
|
func (keyring *Keyring) Lock() {
|
||||||
keyring.AccountKey = nil
|
keyringLog.Info("Locking keyring")
|
||||||
keyring.AsymmetricEncyryptionKey = AsymmetricEncryptionKey{}
|
keyring.isLocked = true
|
||||||
|
keyring.accountKey = nil
|
||||||
|
keyring.AsymmetricEncyryptionKey = MemoryAsymmetricEncryptionKey{}
|
||||||
keyring.OrganizationKeys = nil
|
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) {
|
func (keyring *Keyring) GetSymmetricKeyForOrganization(uuid string) (SymmetricEncryptionKey, error) {
|
||||||
if key, ok := keyring.OrganizationKeys[uuid]; ok {
|
if key, ok := keyring.OrganizationKeys[uuid]; ok {
|
||||||
decryptedOrgKey, err := DecryptWithAsymmetric([]byte(key), keyring.AsymmetricEncyryptionKey)
|
decryptedOrgKey, err := DecryptWithAsymmetric([]byte(key), keyring.AsymmetricEncyryptionKey)
|
||||||
if err != nil {
|
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")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,5 +150,5 @@ func (cipher Cipher) GetKeyForCipher(keyring crypto.Keyring) (crypto.SymmetricEn
|
||||||
if cipher.OrganizationID != nil {
|
if cipher.OrganizationID != nil {
|
||||||
return keyring.GetSymmetricKeyForOrganization(cipher.OrganizationID.String())
|
return keyring.GetSymmetricKeyForOrganization(cipher.OrganizationID.String())
|
||||||
}
|
}
|
||||||
return *keyring.AccountKey, nil
|
return keyring.GetAccountKey(), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ func CreateAuthResponse(ctx context.Context, authRequest AuthRequestData, keyrin
|
||||||
//}
|
//}
|
||||||
|
|
||||||
publicKey, err := base64.StdEncoding.DecodeString(authRequest.PublicKey)
|
publicKey, err := base64.StdEncoding.DecodeString(authRequest.PublicKey)
|
||||||
requesterKey, err := crypto.AssymmetricEncryptionKeyFromBytes(publicKey)
|
requesterKey, err := crypto.MemoryAssymmetricEncryptionKeyFromBytes(publicKey)
|
||||||
|
|
||||||
encryptedUserSymmetricKey, err := crypto.EncryptWithAsymmetric(userSymmetricKey, requesterKey)
|
encryptedUserSymmetricKey, err := crypto.EncryptWithAsymmetric(userSymmetricKey, requesterKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
func DoFullSync(ctx context.Context, vault *vault.Vault, config *config.Config, userSymmetricKey *crypto.SymmetricEncryptionKey, allowCache bool) error {
|
||||||
log.Info("Performing full sync...")
|
log.Info("Performing full sync...")
|
||||||
|
|
||||||
sync, err := Sync(ctx, config)
|
sync, err := Sync(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Could not sync: %v", err)
|
||||||
if allowCache {
|
if allowCache {
|
||||||
home, _ := os.UserHomeDir()
|
home, _ := os.UserHomeDir()
|
||||||
sync, err = ReadVault(home + path)
|
sync, err = ReadVault(home + path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.Info("Sync successful, initializing keyring and vault...")
|
||||||
}
|
}
|
||||||
|
|
||||||
var orgKeys map[string]string = make(map[string]string)
|
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 {
|
for _, org := range sync.Profile.Organizations {
|
||||||
orgId := org.Id.String()
|
orgId := org.Id.String()
|
||||||
orgKeys[orgId] = org.Key
|
orgKeys[orgId] = org.Key
|
||||||
}
|
}
|
||||||
if userSymmetricKey != nil {
|
if userSymmetricKey != nil {
|
||||||
|
log.Info("Initializing keyring from user symmetric key...")
|
||||||
crypto.InitKeyringFromUserSymmetricKey(vault.Keyring, *userSymmetricKey, sync.Profile.PrivateKey, orgKeys)
|
crypto.InitKeyringFromUserSymmetricKey(vault.Keyring, *userSymmetricKey, sync.Profile.PrivateKey, orgKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("Clearing vault...")
|
||||||
vault.Clear()
|
vault.Clear()
|
||||||
|
log.Info("Adding %d ciphers to vault...", len(sync.Ciphers))
|
||||||
for _, cipher := range sync.Ciphers {
|
for _, cipher := range sync.Ciphers {
|
||||||
switch cipher.Type {
|
switch cipher.Type {
|
||||||
case models.CipherLogin:
|
case models.CipherLogin:
|
||||||
vault.AddOrUpdateLogin(cipher)
|
vault.AddOrUpdateLogin(cipher)
|
||||||
break
|
|
||||||
case models.CipherNote:
|
case models.CipherNote:
|
||||||
vault.AddOrUpdateSecureNote(cipher)
|
vault.AddOrUpdateSecureNote(cipher)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package bitwarden
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
@ -49,14 +48,14 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
WEBSOCKET_SLEEP_DURATION_SECONDS = 5
|
WEBSOCKET_SLEEP_DURATION_SECONDS = 60
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunWebsocketDaemon(ctx context.Context, vault *vault.Vault, cfg *config.Config) {
|
func RunWebsocketDaemon(ctx context.Context, vault *vault.Vault, cfg *config.Config) {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
for {
|
for {
|
||||||
time.Sleep(WEBSOCKET_SLEEP_DURATION_SECONDS * time.Second)
|
|
||||||
|
|
||||||
if cfg.IsLocked() {
|
if cfg.IsLocked() {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,6 +65,8 @@ func RunWebsocketDaemon(ctx context.Context, vault *vault.Vault, cfg *config.Con
|
||||||
websocketLog.Error("Websocket error %s", err)
|
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() {
|
go func() {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
for {
|
for {
|
||||||
mt, message, err := c.ReadMessage()
|
_, message, err := c.ReadMessage()
|
||||||
fmt.Println(mt)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
websocketLog.Error("Error reading websocket message %s", err)
|
websocketLog.Error("Error reading websocket message %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(message) < 3 {
|
||||||
|
//ignore empty messages
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if messageType, cipherid, success := websocketMessageType(message); success {
|
if messageType, cipherid, success := websocketMessageType(message); success {
|
||||||
var mt1 = NotificationMessageType(messageType)
|
var mt1 = NotificationMessageType(messageType)
|
||||||
|
|
@ -169,7 +173,11 @@ func connectToWebsocket(ctx context.Context, vault *vault.Vault, cfg *config.Con
|
||||||
break
|
break
|
||||||
case LogOut:
|
case LogOut:
|
||||||
websocketLog.Info("LogOut received. Wiping vault and exiting...")
|
websocketLog.Info("LogOut received. Wiping vault and exiting...")
|
||||||
memguard.SafeExit(0)
|
if vault.Keyring.IsMemguard {
|
||||||
|
memguard.SafeExit(0)
|
||||||
|
} else {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
case AuthRequest:
|
case AuthRequest:
|
||||||
websocketLog.Info("AuthRequest received" + string(cipherid))
|
websocketLog.Info("AuthRequest received" + string(cipherid))
|
||||||
authRequest, err := GetAuthRequest(context.WithValue(ctx, AuthToken{}, token.AccessToken), cipherid, cfg)
|
authRequest, err := GetAuthRequest(context.WithValue(ctx, AuthToken{}, token.AccessToken), cipherid, cfg)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
cryptoSubtle "crypto/subtle"
|
cryptoSubtle "crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
@ -10,7 +11,6 @@ import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/awnumar/memguard"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
||||||
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
|
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
|
||||||
|
|
@ -43,6 +43,7 @@ type RuntimeConfig struct {
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
Pin string
|
Pin string
|
||||||
|
UseMemguard bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
|
|
@ -67,15 +68,18 @@ type LoginToken struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
key *memguard.LockedBuffer
|
useMemguard bool
|
||||||
ConfigFile ConfigFile
|
key *LockedBuffer
|
||||||
mu sync.Mutex
|
ConfigFile ConfigFile
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() Config {
|
func DefaultConfig(useMemguard bool) Config {
|
||||||
deviceUUID, _ := uuid.NewUUID()
|
deviceUUID, _ := uuid.NewUUID()
|
||||||
|
keyBuffer := NewBuffer(32, useMemguard)
|
||||||
return Config{
|
return Config{
|
||||||
memguard.NewBuffer(32),
|
useMemguard,
|
||||||
|
&keyBuffer,
|
||||||
ConfigFile{
|
ConfigFile{
|
||||||
IdentityUrl: "https://vault.bitwarden.com/identity",
|
IdentityUrl: "https://vault.bitwarden.com/identity",
|
||||||
ApiUrl: "https://vault.bitwarden.com/api",
|
ApiUrl: "https://vault.bitwarden.com/api",
|
||||||
|
|
@ -93,7 +97,8 @@ func DefaultConfig() Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) IsLocked() bool {
|
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 {
|
func (c *Config) IsLoggedIn() bool {
|
||||||
|
|
@ -116,7 +121,8 @@ func (c *Config) Unlock(password string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
c.key = memguard.NewBufferFromBytes(key)
|
keyBuffer := NewBufferFromBytes(key, c.useMemguard)
|
||||||
|
c.key = &keyBuffer
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +145,7 @@ func (c *Config) Lock() {
|
||||||
if c.IsLocked() {
|
if c.IsLocked() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.key.Wipe()
|
(*c.key).Wipe()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Purge() {
|
func (c *Config) Purge() {
|
||||||
|
|
@ -151,7 +157,8 @@ func (c *Config) Purge() {
|
||||||
c.ConfigFile.EncryptedUserSymmetricKey = ""
|
c.ConfigFile.EncryptedUserSymmetricKey = ""
|
||||||
c.ConfigFile.ConfigKeyHash = ""
|
c.ConfigFile.ConfigKeyHash = ""
|
||||||
c.ConfigFile.EncryptedMasterKey = ""
|
c.ConfigFile.EncryptedMasterKey = ""
|
||||||
c.key = memguard.NewBuffer(32)
|
key := NewBuffer(32, c.useMemguard)
|
||||||
|
c.key = &key
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) HasPin() bool {
|
func (c *Config) HasPin() bool {
|
||||||
|
|
@ -173,7 +180,8 @@ func (c *Config) UpdatePin(password string, write bool) {
|
||||||
plaintextEncryptedMasterPasswordHash, err4 := c.decryptString(c.ConfigFile.EncryptedMasterPasswordHash)
|
plaintextEncryptedMasterPasswordHash, err4 := c.decryptString(c.ConfigFile.EncryptedMasterPasswordHash)
|
||||||
plaintextMasterKey, err5 := c.decryptString(c.ConfigFile.EncryptedMasterKey)
|
plaintextMasterKey, err5 := c.decryptString(c.ConfigFile.EncryptedMasterKey)
|
||||||
|
|
||||||
c.key = memguard.NewBufferFromBytes(newKey)
|
key := NewBufferFromBytes(newKey, c.useMemguard)
|
||||||
|
c.key = &key
|
||||||
|
|
||||||
if err1 == nil {
|
if err1 == nil {
|
||||||
c.ConfigFile.EncryptedToken, err1 = c.encryptString(plaintextToken)
|
c.ConfigFile.EncryptedToken, err1 = c.encryptString(plaintextToken)
|
||||||
|
|
@ -314,7 +322,7 @@ func (c *Config) encryptString(data string) (string, error) {
|
||||||
if c.IsLocked() {
|
if c.IsLocked() {
|
||||||
return "", errors.New("config is locked")
|
return "", errors.New("config is locked")
|
||||||
}
|
}
|
||||||
ca, err := subtle.NewChaCha20Poly1305(c.key.Bytes())
|
ca, err := subtle.NewChaCha20Poly1305((*c.key).Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -336,7 +344,7 @@ func (c *Config) decryptString(data string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ca, err := subtle.NewChaCha20Poly1305(c.key.Bytes())
|
ca, err := subtle.NewChaCha20Poly1305((*c.key).Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -378,8 +386,9 @@ func (config *Config) WriteConfig() error {
|
||||||
func ReadConfig(rtCfg RuntimeConfig) (Config, error) {
|
func ReadConfig(rtCfg RuntimeConfig) (Config, error) {
|
||||||
file, err := os.Open(rtCfg.ConfigDirectory)
|
file, err := os.Open(rtCfg.ConfigDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
key := NewBuffer(32, rtCfg.UseMemguard)
|
||||||
return Config{
|
return Config{
|
||||||
key: memguard.NewBuffer(32),
|
key: &key,
|
||||||
ConfigFile: ConfigFile{},
|
ConfigFile: ConfigFile{},
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
@ -389,19 +398,22 @@ func ReadConfig(rtCfg RuntimeConfig) (Config, error) {
|
||||||
config := ConfigFile{}
|
config := ConfigFile{}
|
||||||
err = decoder.Decode(&config)
|
err = decoder.Decode(&config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
key := NewBuffer(32, rtCfg.UseMemguard)
|
||||||
return Config{
|
return Config{
|
||||||
key: memguard.NewBuffer(32),
|
key: &key,
|
||||||
ConfigFile: ConfigFile{},
|
ConfigFile: ConfigFile{},
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
if config.ConfigKeyHash == "" {
|
if config.ConfigKeyHash == "" {
|
||||||
|
key := NewBuffer(32, rtCfg.UseMemguard)
|
||||||
return Config{
|
return Config{
|
||||||
key: memguard.NewBuffer(32),
|
key: &key,
|
||||||
ConfigFile: config,
|
ConfigFile: config,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
key := NewBuffer(32, rtCfg.UseMemguard)
|
||||||
return Config{
|
return Config{
|
||||||
key: memguard.NewBuffer(32),
|
key: &key,
|
||||||
ConfigFile: config,
|
ConfigFile: config,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -419,11 +431,17 @@ func (cfg *Config) TryUnlock(vault *vault.Vault) error {
|
||||||
if cfg.IsLoggedIn() {
|
if cfg.IsLoggedIn() {
|
||||||
userKey, err := cfg.GetUserSymmetricKey()
|
userKey, err := cfg.GetUserSymmetricKey()
|
||||||
if err == nil {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
vault.Keyring.AccountKey = &key
|
vault.Keyring.UnlockWithAccountKey(key)
|
||||||
} else {
|
} else {
|
||||||
cfg.Lock()
|
cfg.Lock()
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
48
agent/config/lockedbuffer.go
Normal file
48
agent/config/lockedbuffer.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import "github.com/awnumar/memguard"
|
||||||
|
|
||||||
|
type LockedBuffer interface {
|
||||||
|
Bytes() []byte
|
||||||
|
Wipe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBuffer(size int, useMemguard bool) LockedBuffer {
|
||||||
|
if useMemguard {
|
||||||
|
return MemGuardLockedBuffer{memguard.NewBuffer(size)}
|
||||||
|
}
|
||||||
|
return MemoryLockedBuffer{make([]byte, size)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBufferFromBytes(bytes []byte, useMemguard bool) LockedBuffer {
|
||||||
|
if useMemguard {
|
||||||
|
return MemGuardLockedBuffer{memguard.NewBufferFromBytes(bytes)}
|
||||||
|
}
|
||||||
|
return MemoryLockedBuffer{bytes}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemGuardLockedBuffer struct {
|
||||||
|
buffer *memguard.LockedBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b MemGuardLockedBuffer) Wipe() {
|
||||||
|
b.buffer.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b MemGuardLockedBuffer) Bytes() []byte {
|
||||||
|
return b.buffer.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemoryLockedBuffer struct {
|
||||||
|
buffer []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b MemoryLockedBuffer) Wipe() {
|
||||||
|
for i := range b.buffer {
|
||||||
|
b.buffer[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b MemoryLockedBuffer) Bytes() []byte {
|
||||||
|
return b.buffer
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !windows && !darwin && !linux
|
//go:build (!windows && !darwin && !linux) || noautofill
|
||||||
|
|
||||||
package agent
|
package agent
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build windows || darwin || linux
|
//go:build (windows || darwin || linux) && !noautofill
|
||||||
|
|
||||||
package agent
|
package agent
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,26 @@ type CallingContext struct {
|
||||||
|
|
||||||
func GetCallingContext(connection net.Conn) CallingContext {
|
func GetCallingContext(connection net.Conn) CallingContext {
|
||||||
creds, err := peercred.Get(connection)
|
creds, err := peercred.Get(connection)
|
||||||
|
errorContext := CallingContext{
|
||||||
|
UserName: "unknown",
|
||||||
|
ProcessName: "unknown",
|
||||||
|
ParentProcessName: "unknown",
|
||||||
|
GrandParentProcessName: "unknown",
|
||||||
|
ProcessPid: 0,
|
||||||
|
ParentProcessPid: 0,
|
||||||
|
GrandParentProcessPid: 0,
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return errorContext
|
||||||
}
|
}
|
||||||
pid, _ := creds.PID()
|
pid, _ := creds.PID()
|
||||||
process, err := gops.FindProcess(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
|
// git is epheremal and spawns ssh-keygen and ssh so we need to anchor to git
|
||||||
if process.Executable() == "ssh-keygen" || process.Executable() == "ssh" {
|
if process.Executable() == "ssh-keygen" || process.Executable() == "ssh" {
|
||||||
|
|
@ -38,22 +53,22 @@ func GetCallingContext(connection net.Conn) CallingContext {
|
||||||
uid, _ := creds.UserID()
|
uid, _ := creds.UserID()
|
||||||
ppid := process.PPid()
|
ppid := process.PPid()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return errorContext
|
||||||
}
|
}
|
||||||
|
|
||||||
parentProcess, err := gops.FindProcess(ppid)
|
parentProcess, err := gops.FindProcess(ppid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return errorContext
|
||||||
}
|
}
|
||||||
|
|
||||||
parentParentProcess, err := gops.FindProcess(parentProcess.PPid())
|
parentParentProcess, err := gops.FindProcess(parentProcess.PPid())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return errorContext
|
||||||
}
|
}
|
||||||
|
|
||||||
username, err := user.LookupId(uid)
|
username, err := user.LookupId(uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return errorContext
|
||||||
}
|
}
|
||||||
|
|
||||||
return CallingContext{
|
return CallingContext{
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,13 @@ func NewSSHKeyCipher(name string, keyring *crypto.Keyring) (models.Cipher, strin
|
||||||
privatePEM := pem.EncodeToMemory(&privBlock)
|
privatePEM := pem.EncodeToMemory(&privBlock)
|
||||||
publicKey, err := ssh.NewPublicKey(pub)
|
publicKey, err := ssh.NewPublicKey(pub)
|
||||||
|
|
||||||
encryptedName, _ := crypto.EncryptWith([]byte(name), 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.AccountKey)
|
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.AccountKey)
|
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.AccountKey)
|
encryptedCustomTypeKey, _ := crypto.EncryptWith([]byte("custom-type"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
|
||||||
encryptedCustomTypeValue, _ := crypto.EncryptWith([]byte("ssh-key"), crypto.AesCbc256_HmacSha256_B64, *keyring.AccountKey)
|
encryptedCustomTypeValue, _ := crypto.EncryptWith([]byte("ssh-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
|
||||||
encryptedPrivateKeyKey, _ := crypto.EncryptWith([]byte("private-key"), crypto.AesCbc256_HmacSha256_B64, *keyring.AccountKey)
|
encryptedPrivateKeyKey, _ := crypto.EncryptWith([]byte("private-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
|
||||||
encryptedPrivateKeyValue, _ := crypto.EncryptWith(privatePEM, crypto.AesCbc256_HmacSha256_B64, *keyring.AccountKey)
|
encryptedPrivateKeyValue, _ := crypto.EncryptWith(privatePEM, crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
|
||||||
|
|
||||||
cipher := models.Cipher{
|
cipher := models.Cipher{
|
||||||
Type: models.CipherNote,
|
Type: models.CipherNote,
|
||||||
|
|
|
||||||
|
|
@ -78,15 +78,18 @@ func BiometricsWorking() bool {
|
||||||
|
|
||||||
authority, err := polkit.NewAuthority()
|
authority, err := polkit.NewAuthority()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Warn("Failed to create polkit authority: %s", err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := authority.EnumerateActions("en")
|
result, err := authority.EnumerateActions("en")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Warn("Failed to enumerate polkit actions: %s", err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
|
log.Warn("No polkit actions found")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ func GetPermission(sessionType SessionType, ctx sockets.CallingContext, config *
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
pin, err := pinentry.GetPassword("Enter PIN", "Biometrics is not available. Enter your pin to authorize this action. "+message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
@ -96,10 +97,10 @@ func GetPermission(sessionType SessionType, ctx sockets.CallingContext, config *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
approval, err := pinentry.GetApproval("Goldwarden authorization", message)
|
// approval, err := pinentry.GetApproval("Goldwarden authorization", message)
|
||||||
if err != nil || !approval {
|
// if err != nil || !approval {
|
||||||
return false, err
|
// return false, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Info("Permission granted, creating session")
|
log.Info("Permission granted, creating session")
|
||||||
sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid, sessionType)
|
sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid, sessionType)
|
||||||
|
|
|
||||||
|
|
@ -99,13 +99,18 @@ type AgentState struct {
|
||||||
func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
|
func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// check if exists
|
var keyring crypto.Keyring
|
||||||
keyring := crypto.NewKeyring(nil)
|
if runtimeConfig.UseMemguard {
|
||||||
|
keyring = crypto.NewMemguardKeyring(nil)
|
||||||
|
} else {
|
||||||
|
keyring = crypto.NewMemoryKeyring(nil)
|
||||||
|
}
|
||||||
|
|
||||||
var vault = vault.NewVault(&keyring)
|
var vault = vault.NewVault(&keyring)
|
||||||
cfg, err := config.ReadConfig(runtimeConfig)
|
cfg, err := config.ReadConfig(runtimeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Could not read config: %s", err.Error())
|
log.Warn("Could not read config: %s", err.Error())
|
||||||
cfg = config.DefaultConfig()
|
cfg = config.DefaultConfig(runtimeConfig.UseMemguard)
|
||||||
cfg.ConfigFile.RuntimeConfig = runtimeConfig
|
cfg.ConfigFile.RuntimeConfig = runtimeConfig
|
||||||
cfg.WriteConfig()
|
cfg.WriteConfig()
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +142,12 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
|
||||||
time.Sleep(60 * time.Second)
|
time.Sleep(60 * time.Second)
|
||||||
continue
|
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)
|
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, &protectedUserSymetricKey, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -179,7 +189,12 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
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)
|
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, &protectedUserSymetricKey, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ import (
|
||||||
|
|
||||||
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
||||||
"github.com/quexten/goldwarden/agent/bitwarden/models"
|
"github.com/quexten/goldwarden/agent/bitwarden/models"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/quexten/goldwarden/logging"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var vaultLog = logging.GetLogger("Goldwarden", "Vault")
|
||||||
|
|
||||||
type Vault struct {
|
type Vault struct {
|
||||||
Keyring *crypto.Keyring
|
Keyring *crypto.Keyring
|
||||||
logins map[string]models.Cipher
|
logins map[string]models.Cipher
|
||||||
|
|
@ -94,7 +96,7 @@ func (vault *Vault) isEnv(cipher models.Cipher) (string, bool) {
|
||||||
|
|
||||||
key, err := cipher.GetKeyForCipher(*vault.Keyring)
|
key, err := cipher.GetKeyForCipher(*vault.Keyring)
|
||||||
if err != nil {
|
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
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,7 +134,7 @@ func (vault *Vault) isSSHKey(cipher models.Cipher) bool {
|
||||||
|
|
||||||
key, err := cipher.GetKeyForCipher(*vault.Keyring)
|
key, err := cipher.GetKeyForCipher(*vault.Keyring)
|
||||||
if err != nil {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,8 +142,12 @@ func (vault *Vault) isSSHKey(cipher models.Cipher) bool {
|
||||||
fieldName, err := crypto.DecryptWith(field.Name, key)
|
fieldName, err := crypto.DecryptWith(field.Name, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cipherID := cipher.ID.String()
|
cipherID := cipher.ID.String()
|
||||||
orgID := cipher.OrganizationID.String()
|
if cipher.OrganizationID != nil {
|
||||||
log.Warn().Err(err).Msg("Failed to decrypt field name with on cipher " + cipherID + " in organization " + orgID)
|
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
|
continue
|
||||||
}
|
}
|
||||||
fieldValue, err := crypto.DecryptWith(field.Value, key)
|
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 {
|
if id, ok := vault.envCredentials[executableName]; ok {
|
||||||
key, err := vault.secureNotes[id].GetKeyForCipher(*vault.Keyring)
|
key, err := vault.secureNotes[id].GetKeyForCipher(*vault.Keyring)
|
||||||
if err != nil {
|
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
|
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)
|
key, err := cipher.GetKeyForCipher(*vault.Keyring)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if name != "" {
|
if name != "" {
|
||||||
|
|
@ -312,7 +318,7 @@ func (vault *Vault) GetLoginByFilter(uuid string, orgId string, name string, use
|
||||||
|
|
||||||
decryptedName, err := crypto.DecryptWith(cipher.Name, key)
|
decryptedName, err := crypto.DecryptWith(cipher.Name, key)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if name != "" && string(decryptedName) != name {
|
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)
|
decryptedUsername, err := crypto.DecryptWith(cipher.Login.Username, key)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if username != "" && string(decryptedUsername) != username {
|
if username != "" && string(decryptedUsername) != username {
|
||||||
|
|
@ -338,7 +344,7 @@ func (vault *Vault) GetLoginByFilter(uuid string, orgId string, name string, use
|
||||||
return cipher, nil
|
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) {
|
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)
|
key, err := cipher.GetKeyForCipher(*vault.Keyring)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
decryptedName, err := crypto.DecryptWith(cipher.Name, key)
|
decryptedName, err := crypto.DecryptWith(cipher.Name, key)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if name != "" && string(decryptedName) != name {
|
if name != "" && string(decryptedName) != name {
|
||||||
|
|
|
||||||
|
|
@ -74,12 +74,17 @@ func serveVirtualAgent(recv chan []byte, send chan []byte, ctx context.Context,
|
||||||
func StartVirtualAgent(runtimeConfig config.RuntimeConfig) (chan []byte, chan []byte) {
|
func StartVirtualAgent(runtimeConfig config.RuntimeConfig) (chan []byte, chan []byte) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// check if exists
|
var keyring crypto.Keyring
|
||||||
keyring := crypto.NewKeyring(nil)
|
if runtimeConfig.UseMemguard {
|
||||||
|
keyring = crypto.NewMemguardKeyring(nil)
|
||||||
|
} else {
|
||||||
|
keyring = crypto.NewMemoryKeyring(nil)
|
||||||
|
}
|
||||||
|
|
||||||
var vault = vault.NewVault(&keyring)
|
var vault = vault.NewVault(&keyring)
|
||||||
cfg, err := config.ReadConfig(runtimeConfig)
|
cfg, err := config.ReadConfig(runtimeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var cfg = config.DefaultConfig()
|
var cfg = config.DefaultConfig(runtimeConfig.UseMemguard)
|
||||||
cfg.WriteConfig()
|
cfg.WriteConfig()
|
||||||
}
|
}
|
||||||
cfg.ConfigFile.RuntimeConfig = runtimeConfig
|
cfg.ConfigFile.RuntimeConfig = runtimeConfig
|
||||||
|
|
@ -106,7 +111,12 @@ func StartVirtualAgent(runtimeConfig config.RuntimeConfig) (chan []byte, chan []
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
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)
|
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, &protectedUserSymetricKey, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -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{})
|
|
||||||
}
|
|
||||||
|
|
@ -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{})
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
//go:build !linux
|
|
||||||
|
|
||||||
package autotype
|
|
||||||
|
|
||||||
func TypeString(text string, layout string) error {
|
|
||||||
return errors.New("Not implemented")
|
|
||||||
}
|
|
||||||
|
|
@ -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}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build linux && !uinput
|
//go:build linux
|
||||||
|
|
||||||
package autotype
|
package autotype
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ var globalID = 0
|
||||||
|
|
||||||
const autoTypeDelay = 1 * time.Millisecond
|
const autoTypeDelay = 1 * time.Millisecond
|
||||||
|
|
||||||
func TypeString(textToType string, layout string) {
|
func TypeString(textToType string) {
|
||||||
bus, err := dbus.SessionBus()
|
bus, err := dbus.SessionBus()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -52,7 +52,7 @@ func readMessageLength(msg []byte) int {
|
||||||
|
|
||||||
func send(msg SendMessage) {
|
func send(msg SendMessage) {
|
||||||
byteMsg := dataToBytes(msg)
|
byteMsg := dataToBytes(msg)
|
||||||
logging.Debugf("Sending message: " + string(byteMsg))
|
logging.Debugf("[SENSITIVE] Sending message: " + string(byteMsg))
|
||||||
writeMessageLength(byteMsg)
|
writeMessageLength(byteMsg)
|
||||||
|
|
||||||
var msgBuf bytes.Buffer
|
var msgBuf bytes.Buffer
|
||||||
|
|
|
||||||
32
browserbiometrics/logging/debuglogger.go
Normal file
32
browserbiometrics/logging/debuglogger.go
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
//go:build !nooplogging
|
||||||
|
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeLog(level string, format string, args ...interface{}) {
|
||||||
|
file, _ := os.OpenFile("/tmp/DELETE_ME.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
// log with date and time
|
||||||
|
file.WriteString("[" + level + "] ")
|
||||||
|
file.WriteString(time.Now().Format("2006-01-02 15:04:05") + " ")
|
||||||
|
file.WriteString(fmt.Sprintf(format, args...))
|
||||||
|
file.WriteString("\n")
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debugf(format string, args ...interface{}) {
|
||||||
|
writeLog("DEBUG", format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
writeLog("ERROR", format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Panicf(format string, args ...interface{}) {
|
||||||
|
writeLog("PANIC", format, args...)
|
||||||
|
panic("")
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !logging
|
//go:build nooplogging
|
||||||
|
|
||||||
package logging
|
package logging
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/quexten/goldwarden/browserbiometrics/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
const appID = "com.quexten.bw-bio-handler"
|
const appID = "com.quexten.bw-bio-handler"
|
||||||
|
|
@ -25,7 +27,9 @@ func Main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logging.Debugf("Starting browserbiometrics")
|
||||||
transportKey = generateTransportKey()
|
transportKey = generateTransportKey()
|
||||||
|
logging.Debugf("Generated transport key")
|
||||||
|
|
||||||
setupCommunication()
|
setupCommunication()
|
||||||
readLoop()
|
readLoop()
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ func readLoop() {
|
||||||
lengthBytes := make([]byte, 4)
|
lengthBytes := make([]byte, 4)
|
||||||
lengthNum := int(0)
|
lengthNum := int(0)
|
||||||
|
|
||||||
|
logging.Debugf("Sending connected message")
|
||||||
send(SendMessage{
|
send(SendMessage{
|
||||||
Command: "connected",
|
Command: "connected",
|
||||||
AppID: appID,
|
AppID: appID,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
logging.Debugf("Starting read loop")
|
||||||
for b, err := s.Read(lengthBytes); b > 0 && err == nil; b, err = s.Read(lengthBytes) {
|
for b, err := s.Read(lengthBytes); b > 0 && err == nil; b, err = s.Read(lengthBytes) {
|
||||||
lengthNum = readMessageLength(lengthBytes)
|
lengthNum = readMessageLength(lengthBytes)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,27 @@
|
||||||
//go:build !noautofill
|
//go:build linux
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/quexten/goldwarden/autofill"
|
"os"
|
||||||
|
|
||||||
|
"github.com/quexten/goldwarden/autotype"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var autofillCmd = &cobra.Command{
|
var autofillCmd = &cobra.Command{
|
||||||
Use: "autofill",
|
Use: "autotype",
|
||||||
Short: "Autofill credentials",
|
Short: "Autotype credentials",
|
||||||
Long: `Autofill credentials`,
|
Long: `Autotype credentials`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
layout := cmd.Flag("layout").Value.String()
|
username, _ := cmd.Flags().GetString("username")
|
||||||
autofill.Run(layout, commandClient)
|
// get pasword from env
|
||||||
|
password := os.Getenv("PASSWORD")
|
||||||
|
autotype.TypeString(username + "\t" + password)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(autofillCmd)
|
rootCmd.AddCommand(autofillCmd)
|
||||||
autofillCmd.PersistentFlags().String("layout", "qwerty", "")
|
autofillCmd.PersistentFlags().String("username", "", "")
|
||||||
autofillCmd.PersistentFlags().Bool("use-copy-paste", false, "")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/icza/gox/stringsx"
|
||||||
|
"github.com/quexten/goldwarden/client"
|
||||||
"github.com/quexten/goldwarden/ipc/messages"
|
"github.com/quexten/goldwarden/ipc/messages"
|
||||||
"github.com/spf13/cobra"
|
"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() {
|
func init() {
|
||||||
rootCmd.AddCommand(baseLoginCmd)
|
rootCmd.AddCommand(baseLoginCmd)
|
||||||
baseLoginCmd.AddCommand(getLoginCmd)
|
baseLoginCmd.AddCommand(getLoginCmd)
|
||||||
|
|
@ -61,4 +121,5 @@ func init() {
|
||||||
getLoginCmd.PersistentFlags().String("username", "", "")
|
getLoginCmd.PersistentFlags().String("username", "", "")
|
||||||
getLoginCmd.PersistentFlags().String("uuid", "", "")
|
getLoginCmd.PersistentFlags().String("uuid", "", "")
|
||||||
getLoginCmd.PersistentFlags().Bool("full", false, "")
|
getLoginCmd.PersistentFlags().Bool("full", false, "")
|
||||||
|
baseLoginCmd.AddCommand(listLoginsCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
30
cmd/vault.go
30
cmd/vault.go
|
|
@ -1,6 +1,8 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/quexten/goldwarden/ipc/messages"
|
"github.com/quexten/goldwarden/ipc/messages"
|
||||||
"github.com/spf13/cobra"
|
"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() {
|
func init() {
|
||||||
rootCmd.AddCommand(vaultCmd)
|
rootCmd.AddCommand(vaultCmd)
|
||||||
vaultCmd.AddCommand(unlockCmd)
|
vaultCmd.AddCommand(unlockCmd)
|
||||||
vaultCmd.AddCommand(lockCmd)
|
vaultCmd.AddCommand(lockCmd)
|
||||||
vaultCmd.AddCommand(purgeCmd)
|
vaultCmd.AddCommand(purgeCmd)
|
||||||
|
vaultCmd.AddCommand(statusCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
com.quexten.Goldwarden.yml
Normal file
31
com.quexten.Goldwarden.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
id: com.quexten.Goldwarden
|
||||||
|
runtime: org.gnome.Platform
|
||||||
|
runtime-version: '45'
|
||||||
|
sdk: org.gnome.Sdk
|
||||||
|
command: main.py
|
||||||
|
finish-args:
|
||||||
|
- --share=ipc
|
||||||
|
- --share=network
|
||||||
|
- --socket=wayland
|
||||||
|
- --socket=fallback-x11
|
||||||
|
- --device=dri
|
||||||
|
# - --filesystem=home
|
||||||
|
- --filesystem=host
|
||||||
|
# polkit & locking.. should filter this later on
|
||||||
|
- --socket=session-bus
|
||||||
|
- --socket=system-bus
|
||||||
|
modules:
|
||||||
|
- name: goldwarden
|
||||||
|
buildsystem: simple
|
||||||
|
build-options:
|
||||||
|
build-args:
|
||||||
|
- "--share=network"
|
||||||
|
build-commands:
|
||||||
|
- pip3 install --prefix=/app tendo
|
||||||
|
- install -D main.py /app/bin/main.py
|
||||||
|
- install -D com.quexten.Goldwarden.desktop /app/share/applications/com.quexten.Goldwarden.desktop
|
||||||
|
- install -D goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg
|
||||||
|
- cp -R ./ /app/bin/
|
||||||
|
sources:
|
||||||
|
- type: dir
|
||||||
|
path: ui
|
||||||
47
go.mod
47
go.mod
|
|
@ -7,22 +7,21 @@ require (
|
||||||
github.com/LlamaNite/llamalog v0.2.1
|
github.com/LlamaNite/llamalog v0.2.1
|
||||||
github.com/amenzhinsky/go-polkit v0.0.0-20210519083301-ee6a51849123
|
github.com/amenzhinsky/go-polkit v0.0.0-20210519083301-ee6a51849123
|
||||||
github.com/atotto/clipboard v0.1.4
|
github.com/atotto/clipboard v0.1.4
|
||||||
github.com/awnumar/memguard v0.22.3
|
github.com/awnumar/memguard v0.22.4
|
||||||
github.com/bendahl/uinput v1.6.2
|
github.com/bendahl/uinput v1.7.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.5.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.1
|
||||||
github.com/lox/go-touchid v0.0.0-20170712105233-619cc8e578d0
|
github.com/lox/go-touchid v0.0.0-20170712105233-619cc8e578d0
|
||||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
|
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
|
||||||
github.com/mitchellh/go-ps v1.0.0
|
github.com/mitchellh/go-ps v1.0.0
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/shirou/gopsutil/v3 v3.23.6
|
github.com/shirou/gopsutil/v3 v3.23.11
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/tink-crypto/tink-go/v2 v2.0.0
|
github.com/tink-crypto/tink-go/v2 v2.1.0
|
||||||
github.com/twpayne/go-pinentry v0.2.0
|
github.com/twpayne/go-pinentry v0.3.0
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||||
golang.org/x/crypto v0.13.0
|
golang.org/x/crypto v0.17.0
|
||||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
|
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848
|
||||||
golang.org/x/sys v0.12.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -31,28 +30,32 @@ require (
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-text/typesetting v0.0.0-20230602202114-9797aefac433 // 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/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
github.com/tklauser/numcpus v0.7.0 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect
|
golang.org/x/exp/shiny v0.0.0-20231219180239-dc181d75b848 // indirect
|
||||||
golang.org/x/image v0.5.0 // indirect
|
golang.org/x/image v0.14.0 // indirect
|
||||||
golang.org/x/text v0.13.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 (
|
require (
|
||||||
github.com/awnumar/memcall v0.1.2 // indirect
|
github.com/awnumar/memcall v0.2.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/keys-pub/go-libfido2 v1.5.3
|
github.com/keys-pub/go-libfido2 v1.5.3
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
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/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/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // 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
|
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
||||||
)
|
)
|
||||||
|
|
|
||||||
137
go.sum
137
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=
|
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 h1:k9XugHmyQqJhCrogca808Jl2rrEKIWMtWyLKX+xX9Mg=
|
||||||
github.com/LlamaNite/llamalog v0.2.1/go.mod h1:zopgmWk8utZPfZCPa/uvQkv99Lan3pRrw/9inbIYZeo=
|
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 h1:VdNhe94PF9yn6KudYnpcBb6bH7l+wsEy9yn6Ulm1/j8=
|
||||||
github.com/amenzhinsky/go-polkit v0.0.0-20210519083301-ee6a51849123/go.mod h1:CdMR3dsiNi5M2BbtFlMo85mRbNt6LiMw04UBzJmoVEU=
|
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 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
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.2.0 h1:sRaogqExTOOkkNwO9pzJsL8jrOV29UuUW7teRMfbqtI=
|
||||||
github.com/awnumar/memcall v0.1.2/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo=
|
github.com/awnumar/memcall v0.2.0/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo=
|
||||||
github.com/awnumar/memguard v0.22.3 h1:b4sgUXtbUjhrGELPbuC62wU+BsPQy+8lkWed9Z+pj0Y=
|
github.com/awnumar/memguard v0.22.4 h1:1PLgKcgGPeExPHL8dCOWGVjIbQUBgJv9OL0F/yE1PqQ=
|
||||||
github.com/awnumar/memguard v0.22.3/go.mod h1:mmGunnffnLHlxE5rRgQc3j+uwPZ27eYb61ccr8Clz2Y=
|
github.com/awnumar/memguard v0.22.4/go.mod h1:+APmZGThMBWjnMlKiSM1X7MVpbIVewen2MTkqWkA/zE=
|
||||||
github.com/bendahl/uinput v1.6.2 h1:tIz52QyKDx1i1nObUkts3AZa/bULfLhPA5a+xKGlRPI=
|
github.com/bendahl/uinput v1.7.0 h1:nA4fm8Wu8UYNOPykIZm66nkWEyvxzfmJ8YC02PM40jg=
|
||||||
github.com/bendahl/uinput v1.6.2/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8=
|
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 h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
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.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/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
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 h1:vtgHxlSB43u6lj0TSuA3VvT6z3E7VI+L1a2hvMFdECk=
|
||||||
github.com/keys-pub/go-libfido2 v1.5.3/go.mod h1:P0V19qHwJNY0htZwZDe9Ilvs/nokGhdFX7faKFyZ6+U=
|
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 h1:m81erW+1MD5vl3lKQ/+TYPHJ6Y9/C1COqxXPE51FkDk=
|
||||||
github.com/lox/go-touchid v0.0.0-20170712105233-619cc8e578d0/go.mod h1:EHbIQzfC3kdWFI81pLOFjssnolF+ALfmVf8PUdWBxo4=
|
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 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
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.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.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 h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
|
||||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
|
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
|
||||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
github.com/mitchellh/go-ps v1.0.0 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/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 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
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/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
|
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||||
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
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/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.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU=
|
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 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
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 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
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.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/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/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.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.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.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.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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
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.1.0 h1:QXFBguwMwTIaU17EgZpEJWsUSc60b1BAGTzBIoMdmok=
|
||||||
github.com/tink-crypto/tink-go/v2 v2.0.0/go.mod h1:QAbyq9LZncomYnScxlfaHImbV4ieNIe6bnu/Xcqqox4=
|
github.com/tink-crypto/tink-go/v2 v2.1.0/go.mod h1:y1TnYFt1i2eZVfx4OGc+C+EMp4CoKWAw2VSEuoicHHI=
|
||||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
||||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
||||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/twpayne/go-pinentry v0.2.0 h1:hS5NEJiilop9xP9pBX/1NYduzDlGGMdg1KamTBTrOWw=
|
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
||||||
github.com/twpayne/go-pinentry v0.2.0/go.mod h1:r6buhMwARxnnL0VRBqfd1tE6Fadk1kfP00GRMutEspY=
|
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
github.com/twpayne/go-pinentry v0.3.0 h1:Rr+fEOZXmeItOb4thjeVaBWJKB9Xa/eojolycyF/26c=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
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 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
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 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
golang.org/x/exp/shiny v0.0.0-20231219180239-dc181d75b848 h1:LnDWUUS+bxOesHc+QBvFFmS4v0ZH+Vtg0EncMANwN9Q=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/exp/shiny v0.0.0-20231219180239-dc181d75b848/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0=
|
||||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
|
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
|
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
|
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||||
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/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-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-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-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-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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
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/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/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
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/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,17 @@ type WipeVaultRequest struct {
|
||||||
type GetVaultPINRequest struct {
|
type GetVaultPINRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VaultStatusRequest struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type VaultStatusResponse struct {
|
||||||
|
Locked bool
|
||||||
|
NumberOfLogins int
|
||||||
|
NumberOfNotes int
|
||||||
|
// todo websocket status
|
||||||
|
// todo last synced
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
registerPayloadParser(func(payload []byte) (interface{}, error) {
|
||||||
var req LockVaultRequest
|
var req LockVaultRequest
|
||||||
|
|
@ -62,4 +73,22 @@ func init() {
|
||||||
}
|
}
|
||||||
return req, nil
|
return req, nil
|
||||||
}, GetVaultPINRequest{})
|
}, 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{})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
main.go
1
main.go
|
|
@ -38,6 +38,7 @@ func main() {
|
||||||
User: os.Getenv("GOLDWARDEN_AUTH_USER"),
|
User: os.Getenv("GOLDWARDEN_AUTH_USER"),
|
||||||
Password: os.Getenv("GOLDWARDEN_AUTH_PASSWORD"),
|
Password: os.Getenv("GOLDWARDEN_AUTH_PASSWORD"),
|
||||||
Pin: os.Getenv("GOLDWARDEN_PIN"),
|
Pin: os.Getenv("GOLDWARDEN_PIN"),
|
||||||
|
UseMemguard: os.Getenv("GOLDWARDEN_NO_MEMGUARD") != "true",
|
||||||
|
|
||||||
ConfigDirectory: configPath,
|
ConfigDirectory: configPath,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
ui/autofill.py
Normal file
108
ui/autofill.py
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '4.0')
|
||||||
|
gi.require_version('Adw', '1')
|
||||||
|
import gc
|
||||||
|
import time
|
||||||
|
from gi.repository import Gtk, Adw, GLib
|
||||||
|
import goldwarden
|
||||||
|
import clipboard
|
||||||
|
from threading import Thread
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
class MyApp(Adw.Application):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.connect('activate', self.on_activate)
|
||||||
|
|
||||||
|
def on_activate(self, app):
|
||||||
|
self.autofill_window = MainWindow(application=app)
|
||||||
|
self.autofill_window.logins = []
|
||||||
|
self.autofill_window.present()
|
||||||
|
logins = goldwarden.get_vault_logins()
|
||||||
|
if logins == None:
|
||||||
|
return
|
||||||
|
app.autofill_window.logins = logins
|
||||||
|
|
||||||
|
class MainWindow(Gtk.ApplicationWindow):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.stack = Gtk.Stack()
|
||||||
|
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
|
||||||
|
self.set_child(self.stack)
|
||||||
|
|
||||||
|
self.box = Gtk.Box()
|
||||||
|
self.box.set_orientation(Gtk.Orientation.VERTICAL)
|
||||||
|
self.stack.add_named(self.box, "box")
|
||||||
|
|
||||||
|
self.text_view = Adw.EntryRow()
|
||||||
|
self.text_view.set_title("Search")
|
||||||
|
# on type func
|
||||||
|
def on_type(entry):
|
||||||
|
if len(entry.get_text()) > 1:
|
||||||
|
self.history_list.show()
|
||||||
|
else:
|
||||||
|
self.history_list.hide()
|
||||||
|
|
||||||
|
while self.history_list.get_first_child() != None:
|
||||||
|
self.history_list.remove(self.history_list.get_first_child())
|
||||||
|
|
||||||
|
self.filtered_logins = list(filter(lambda i: entry.get_text().lower() in i["name"].lower(), self.logins))
|
||||||
|
if len( self.filtered_logins) > 10:
|
||||||
|
self.filtered_logins = self.filtered_logins[0:10]
|
||||||
|
self.starts_with_logins = list(filter(lambda i: i["name"].lower().startswith(entry.get_text().lower()), self.logins))
|
||||||
|
self.other_logins = list(filter(lambda i: i not in self.starts_with_logins , self.filtered_logins))
|
||||||
|
self.filtered_logins = None
|
||||||
|
|
||||||
|
for i in self.starts_with_logins + self.other_logins :
|
||||||
|
action_row = Adw.ActionRow()
|
||||||
|
action_row.set_title(i["name"])
|
||||||
|
action_row.set_subtitle(i["username"])
|
||||||
|
action_row.set_icon_name("dialog-password")
|
||||||
|
action_row.set_activatable(True)
|
||||||
|
action_row.password = i["password"]
|
||||||
|
action_row.username = i["username"]
|
||||||
|
self.history_list.append(action_row)
|
||||||
|
self.starts_with_logins = None
|
||||||
|
self.other_logins = None
|
||||||
|
self.text_view.connect("changed", lambda entry: on_type(entry))
|
||||||
|
self.box.append(self.text_view)
|
||||||
|
|
||||||
|
self.history_list = Gtk.ListBox()
|
||||||
|
# margin'
|
||||||
|
self.history_list.set_margin_start(10)
|
||||||
|
self.history_list.set_margin_end(10)
|
||||||
|
self.history_list.set_margin_top(10)
|
||||||
|
self.history_list.set_margin_bottom(10)
|
||||||
|
self.history_list.hide()
|
||||||
|
|
||||||
|
keycont = Gtk.EventControllerKey()
|
||||||
|
def handle_keypress(cotroller, keyval, keycode, state, user_data):
|
||||||
|
if keycode == 36:
|
||||||
|
print("enter")
|
||||||
|
self.hide()
|
||||||
|
def do_autotype(username, password):
|
||||||
|
time.sleep(0.5)
|
||||||
|
goldwarden.autotype(username, password)
|
||||||
|
GLib.idle_add(lambda: self.show())
|
||||||
|
autotypeThread = Thread(target=do_autotype, args=(self.history_list.get_selected_row().username, self.history_list.get_selected_row().password,))
|
||||||
|
autotypeThread.start()
|
||||||
|
print(self.history_list.get_selected_row().get_title())
|
||||||
|
if keyval == 112:
|
||||||
|
print("copy password")
|
||||||
|
clipboard.write(self.history_list.get_selected_row().password)
|
||||||
|
elif keyval == 117:
|
||||||
|
print("copy username")
|
||||||
|
clipboard.write(self.history_list.get_selected_row().username)
|
||||||
|
|
||||||
|
keycont.connect('key-pressed', handle_keypress, self)
|
||||||
|
self.add_controller(keycont)
|
||||||
|
|
||||||
|
self.history_list.get_style_context().add_class("boxed-list")
|
||||||
|
self.box.append(self.history_list)
|
||||||
|
self.set_default_size(700, 700)
|
||||||
|
self.set_title("Goldwarden")
|
||||||
|
|
||||||
|
app = MyApp(application_id="com.quexten.Goldwarden.autofill")
|
||||||
|
app.run(sys.argv)
|
||||||
101
ui/autotype/autotype.py
Normal file
101
ui/autotype/autotype.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# TODO??!?!? for now using golang implementation
|
||||||
|
|
||||||
|
import dbus
|
||||||
|
import dbus.mainloop.glib
|
||||||
|
from dbus.mainloop.glib import DBusGMainLoop
|
||||||
|
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
|
step = 0
|
||||||
|
|
||||||
|
def typestring(text):
|
||||||
|
step = 0
|
||||||
|
handle = ""
|
||||||
|
|
||||||
|
def handler(*args, **kwargs):
|
||||||
|
global step
|
||||||
|
if step == 0:
|
||||||
|
handle_xdp_session_created(*args, **kwargs)
|
||||||
|
elif step == 1:
|
||||||
|
handle_xdp_devices_selected(*args, **kwargs)
|
||||||
|
elif step == 2:
|
||||||
|
handle_session_start(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
print(args, kwargs)
|
||||||
|
step += 1
|
||||||
|
|
||||||
|
def handle_session_start(code, results, object_path):
|
||||||
|
global handle
|
||||||
|
|
||||||
|
if code != 0:
|
||||||
|
raise Exception("Could not start session")
|
||||||
|
|
||||||
|
for sym in text:
|
||||||
|
if sym == "\t":
|
||||||
|
inter.NotifyKeyboardKeycode(handle, {}, 15, 1)
|
||||||
|
time.sleep(0.001)
|
||||||
|
inter.NotifyKeyboardKeycode(handle, {}, 15, 0)
|
||||||
|
time.sleep(0.001)
|
||||||
|
else:
|
||||||
|
inter.NotifyKeyboardKeysym(handle, {}, ord(sym), 1)
|
||||||
|
time.sleep(0.001)
|
||||||
|
inter.NotifyKeyboardKeysym(handle, {}, ord(sym), 0)
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
bus
|
||||||
|
|
||||||
|
def handle_xdp_devices_selected(code, results, object_path):
|
||||||
|
global handle
|
||||||
|
|
||||||
|
if code != 0:
|
||||||
|
raise Exception("Could not select devices")
|
||||||
|
|
||||||
|
start_options = {
|
||||||
|
"handle_token": "krfb" + str(random.randint(0, 999999999))
|
||||||
|
}
|
||||||
|
reply = inter.Start(handle, "", start_options)
|
||||||
|
print(reply)
|
||||||
|
|
||||||
|
def handle_xdp_session_created(code, results, object_path):
|
||||||
|
global handle
|
||||||
|
|
||||||
|
if code != 0:
|
||||||
|
raise Exception("Could not create session")
|
||||||
|
print(results)
|
||||||
|
handle = results["session_handle"]
|
||||||
|
|
||||||
|
# select sources for the session
|
||||||
|
selection_options = {
|
||||||
|
"types": dbus.UInt32(7), # request all (KeyBoard, Pointer, TouchScreen)
|
||||||
|
"handle_token": "krfb" + str(random.randint(0, 999999999))
|
||||||
|
}
|
||||||
|
selector_reply = inter.SelectDevices(handle, selection_options)
|
||||||
|
print(selector_reply)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global bus
|
||||||
|
global inter
|
||||||
|
loop = GLib.MainLoop()
|
||||||
|
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||||
|
bus = dbus.SessionBus()
|
||||||
|
obj = bus.get_object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop")
|
||||||
|
inter = dbus.Interface(obj, "org.freedesktop.portal.RemoteDesktop")
|
||||||
|
|
||||||
|
bus.add_signal_receiver(
|
||||||
|
handler,
|
||||||
|
signal_name="Response",
|
||||||
|
dbus_interface="org.freedesktop.portal.Request",
|
||||||
|
bus_name="org.freedesktop.portal.Desktop",
|
||||||
|
path_keyword="object_path")
|
||||||
|
|
||||||
|
print(inter)
|
||||||
|
result = inter.CreateSession({
|
||||||
|
"session_handle_token": "sessionhandletoken"
|
||||||
|
})
|
||||||
|
print(result)
|
||||||
|
loop.run()
|
||||||
|
|
||||||
|
main()
|
||||||
58
ui/background.py
Normal file
58
ui/background.py
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '4.0')
|
||||||
|
gi.require_version('Adw', '1')
|
||||||
|
import gc
|
||||||
|
from gi.repository import Gtk, Adw, GLib, Gio
|
||||||
|
from random import randint
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def receive_autostart(self, *args):
|
||||||
|
print("autostart enabled..!?")
|
||||||
|
print(args)
|
||||||
|
|
||||||
|
def request_autostart():
|
||||||
|
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||||
|
proxy = Gio.DBusProxy.new_sync(
|
||||||
|
bus,
|
||||||
|
Gio.DBusProxyFlags.NONE,
|
||||||
|
None,
|
||||||
|
'org.freedesktop.portal.Desktop',
|
||||||
|
'/org/freedesktop/portal/desktop',
|
||||||
|
'org.freedesktop.portal.Background',
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
token = 0 + randint(10000000, 20000000)
|
||||||
|
options = {
|
||||||
|
'handle_token': GLib.Variant('s', f'com/quexten/Goldwarden/{token}'),
|
||||||
|
'reason': GLib.Variant('s', ('Autostart Goldwarden in the background.')),
|
||||||
|
'autostart': GLib.Variant('b', True),
|
||||||
|
'commandline': GLib.Variant('as', ['main.py', '--hidden']),
|
||||||
|
'dbus-activatable': GLib.Variant('b', False),
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
request = proxy.RequestBackground('(sa{sv})', "", options)
|
||||||
|
if request is None:
|
||||||
|
raise Exception(
|
||||||
|
"Registering with background portal failed."
|
||||||
|
)
|
||||||
|
|
||||||
|
bus.signal_subscribe(
|
||||||
|
'org.freedesktop.portal.Desktop',
|
||||||
|
'org.freedesktop.portal.Request',
|
||||||
|
'Response',
|
||||||
|
request,
|
||||||
|
None,
|
||||||
|
Gio.DBusSignalFlags.NO_MATCH_RULE,
|
||||||
|
receive_autostart,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
request_autostart()
|
||||||
|
|
||||||
|
loop = GLib.MainLoop()
|
||||||
|
loop.run()
|
||||||
5
ui/clipboard.py
Normal file
5
ui/clipboard.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def write(text):
|
||||||
|
process = subprocess.Popen(["/bin/sh", "-c", "wl-copy"], stdin=subprocess.PIPE)
|
||||||
|
process.communicate(text.encode('utf-8'))
|
||||||
10
ui/com.quexten.Goldwarden.desktop
Normal file
10
ui/com.quexten.Goldwarden.desktop
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Goldwarden
|
||||||
|
Comment=A Bitwarden compatible desktop password manager
|
||||||
|
Keywords=backup;recovery;
|
||||||
|
Exec=main.py
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Icon=com.quexten.Goldwarden.svg
|
||||||
|
StartupNotify=true
|
||||||
|
Categories=GNOME;GTK;Password Manager;
|
||||||
129
ui/goldwarden.py
Normal file
129
ui/goldwarden.py
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
BINARY_PATH = "/home/quexten/go/src/github.com/quexten/goldwarden/goldwarden"
|
||||||
|
|
||||||
|
def set_api_url(url):
|
||||||
|
restic_cmd = f"{BINARY_PATH} config set-api-url {url}"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
|
||||||
|
def set_identity_url(url):
|
||||||
|
restic_cmd = f"{BINARY_PATH} config set-identity-url {url}"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
|
||||||
|
def set_notification_url(url):
|
||||||
|
restic_cmd = f"{BINARY_PATH} config set-notifications-url {url}"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
|
||||||
|
def login_with_password(email, password):
|
||||||
|
restic_cmd = f"{BINARY_PATH} vault login --email {email}"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
if len(result.stderr.strip()) > 0:
|
||||||
|
print(result.stderr)
|
||||||
|
if "password" in result.stderr:
|
||||||
|
return "badpass"
|
||||||
|
else:
|
||||||
|
if "Logged in" in result.stderr:
|
||||||
|
print("ok")
|
||||||
|
return "ok"
|
||||||
|
return "error"
|
||||||
|
print("ok")
|
||||||
|
return "ok"
|
||||||
|
|
||||||
|
def login_passwordless(email):
|
||||||
|
restic_cmd = f"{BINARY_PATH} vault login --email {email} --passwordless"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
|
||||||
|
def is_pin_enabled():
|
||||||
|
restic_cmd = f"{BINARY_PATH} vault pin status"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
# check if contains enabled
|
||||||
|
return "enabled" in result.stderr
|
||||||
|
|
||||||
|
def enable_pin():
|
||||||
|
restic_cmd = f"{BINARY_PATH} vault pin set"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
|
||||||
|
def unlock():
|
||||||
|
restic_cmd = f"{BINARY_PATH} vault unlock"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
|
||||||
|
def lock():
|
||||||
|
restic_cmd = f"{BINARY_PATH} vault lock"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
|
||||||
|
def purge():
|
||||||
|
restic_cmd = f"{BINARY_PATH} vault purge"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
|
||||||
|
def get_vault_status():
|
||||||
|
restic_cmd = f"{BINARY_PATH} vault status"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
try:
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_vault_logins():
|
||||||
|
restic_cmd = f"{BINARY_PATH} logins list"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
try:
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def autotype(username, password):
|
||||||
|
# environment
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["PASSWORD"] = password
|
||||||
|
restic_cmd = f"{BINARY_PATH} autotype --username {username}"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True, env=env)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
||||||
|
|
||||||
|
def is_daemon_running():
|
||||||
|
restic_cmd = f"{BINARY_PATH} vault status"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return False
|
||||||
|
daemon_not_running = ("daemon running?" in result.stderr or "daemon running" in result.stderr)
|
||||||
|
return not daemon_not_running
|
||||||
|
|
||||||
|
def run_daemon():
|
||||||
|
restic_cmd = f"{BINARY_PATH} daemonize"
|
||||||
|
# print while running
|
||||||
|
result = subprocess.Popen(restic_cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("Failed err", result.stderr)
|
||||||
|
for line in result.stdout:
|
||||||
|
print(line.decode("utf-8"))
|
||||||
|
result.wait()
|
||||||
|
print("quitting goldwarden daemon")
|
||||||
|
return result.returncode
|
||||||
115
ui/goldwarden.svg
Normal file
115
ui/goldwarden.svg
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="100mm"
|
||||||
|
height="100mm"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
|
||||||
|
sodipodi:docname="goldwarden.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="1.6792579"
|
||||||
|
inkscape:cx="142.62253"
|
||||||
|
inkscape:cy="292.09332"
|
||||||
|
inkscape:window-width="3840"
|
||||||
|
inkscape:window-height="2091"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient17"
|
||||||
|
inkscape:collect="always">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffeb28;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop17" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffb608;stop-opacity:1;"
|
||||||
|
offset="0.76853603"
|
||||||
|
id="stop18" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffa21f;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop19" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient17"
|
||||||
|
id="radialGradient18"
|
||||||
|
cx="66.050179"
|
||||||
|
cy="50.758305"
|
||||||
|
fx="66.050179"
|
||||||
|
fy="50.758305"
|
||||||
|
r="45"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(1.1205298,9.6606045e-4,-7.5884389e-4,0.88018377,-7.9225014,6.0178603)" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:url(#radialGradient18);fill-opacity:1;stroke-width:0.262037;fill-rule:nonzero"
|
||||||
|
id="rect1"
|
||||||
|
width="90"
|
||||||
|
height="90"
|
||||||
|
x="5.0169253"
|
||||||
|
y="4.8409019"
|
||||||
|
ry="12.342399" />
|
||||||
|
<g
|
||||||
|
id="g17"
|
||||||
|
transform="matrix(1.0914831,0,0,1.0914831,-10.347732,-4.5673979)">
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.279194"
|
||||||
|
id="path1"
|
||||||
|
cx="70"
|
||||||
|
cy="50"
|
||||||
|
r="10" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.200309"
|
||||||
|
id="rect2"
|
||||||
|
width="5"
|
||||||
|
height="50"
|
||||||
|
x="67.5"
|
||||||
|
y="24.926144"
|
||||||
|
ry="2.0560377" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.200309"
|
||||||
|
id="rect3"
|
||||||
|
width="5"
|
||||||
|
height="50"
|
||||||
|
x="-10.85097"
|
||||||
|
y="60.616943"
|
||||||
|
ry="2.0560377"
|
||||||
|
transform="rotate(-60.033259)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.200309"
|
||||||
|
id="rect4"
|
||||||
|
width="5"
|
||||||
|
height="50"
|
||||||
|
x="-80.843193"
|
||||||
|
y="10.529473"
|
||||||
|
ry="2.0560377"
|
||||||
|
transform="rotate(-120.06752)" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
47
ui/main.py
Normal file
47
ui/main.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
from tendo import singleton
|
||||||
|
import monitors.dbus_autofill_monitor
|
||||||
|
import sys
|
||||||
|
import goldwarden
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.Popen(["python3", "/app/bin/background.py"], start_new_session=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
is_hidden = "--hidden" in sys.argv
|
||||||
|
|
||||||
|
if not is_hidden:
|
||||||
|
try:
|
||||||
|
subprocess.Popen(["python3", "/app/bin/settings.py"], start_new_session=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
me = singleton.SingleInstance()
|
||||||
|
except:
|
||||||
|
exit()
|
||||||
|
|
||||||
|
def run_daemon():
|
||||||
|
# todo: do a proper check
|
||||||
|
if is_hidden:
|
||||||
|
time.sleep(20)
|
||||||
|
if not goldwarden.is_daemon_running():
|
||||||
|
goldwarden.run_daemon()
|
||||||
|
|
||||||
|
if not goldwarden.is_daemon_running():
|
||||||
|
print("daemon not running.. autostarting")
|
||||||
|
daemonThread = Thread(target=run_daemon)
|
||||||
|
daemonThread.start()
|
||||||
|
|
||||||
|
print("starting autofill monitor")
|
||||||
|
|
||||||
|
def on_autofill():
|
||||||
|
subprocess.Popen(["python3", "/app/bin/autofill.py"], start_new_session=True)
|
||||||
|
monitors.dbus_autofill_monitor.on_autofill = on_autofill
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(60)
|
||||||
23
ui/monitors/dbus_autofill_monitor.py
Normal file
23
ui/monitors/dbus_autofill_monitor.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#Python DBUS Test Server
|
||||||
|
#runs until the Quit() method is called via DBUS
|
||||||
|
|
||||||
|
from gi.repository import Gtk
|
||||||
|
import dbus
|
||||||
|
import dbus.service
|
||||||
|
from dbus.mainloop.glib import DBusGMainLoop
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
on_autofill = lambda: None
|
||||||
|
|
||||||
|
class GoldwardenDBUSService(dbus.service.Object):
|
||||||
|
def __init__(self):
|
||||||
|
bus_name = dbus.service.BusName('com.quexten.goldwarden', bus=dbus.SessionBus())
|
||||||
|
dbus.service.Object.__init__(self, bus_name, '/com/quexten/goldwarden')
|
||||||
|
|
||||||
|
@dbus.service.method('com.quexten.goldwarden.Autofill')
|
||||||
|
def autofill(self):
|
||||||
|
on_autofill()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
DBusGMainLoop(set_as_default=True)
|
||||||
|
service = GoldwardenDBUSService()
|
||||||
1
ui/monitors/global_shortcut_portal.py
Normal file
1
ui/monitors/global_shortcut_portal.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
# todo
|
||||||
221
ui/settings.py
Normal file
221
ui/settings.py
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
import sys
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '4.0')
|
||||||
|
gi.require_version('Adw', '1')
|
||||||
|
import gc
|
||||||
|
|
||||||
|
from gi.repository import Gtk, Adw, GLib
|
||||||
|
import goldwarden
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
class SettingsWinvdow(Gtk.ApplicationWindow):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.stack = Gtk.Stack()
|
||||||
|
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
|
||||||
|
self.set_child(self.stack)
|
||||||
|
|
||||||
|
self.preferences_page = Adw.PreferencesPage()
|
||||||
|
self.preferences_page.set_title("General")
|
||||||
|
self.stack.add_named(self.preferences_page, "preferences_page")
|
||||||
|
|
||||||
|
self.preferences_group = Adw.PreferencesGroup()
|
||||||
|
self.preferences_group.set_title("Services")
|
||||||
|
self.preferences_page.add(self.preferences_group)
|
||||||
|
|
||||||
|
self.ssh_row = Adw.ActionRow()
|
||||||
|
self.ssh_row.set_title("SSH Daemon")
|
||||||
|
self.ssh_row.set_subtitle("Listening at ~/.goldwarden-ssh-agent.sock")
|
||||||
|
self.preferences_group.add(self.ssh_row)
|
||||||
|
|
||||||
|
self.login_with_device = Adw.ActionRow()
|
||||||
|
self.login_with_device.set_title("Login with device")
|
||||||
|
self.login_with_device.set_subtitle("Waiting for requests...")
|
||||||
|
self.preferences_group.add(self.login_with_device)
|
||||||
|
|
||||||
|
self.status_row = Adw.ActionRow()
|
||||||
|
self.status_row.set_title("DBUS Service")
|
||||||
|
self.status_row.set_subtitle("Listening")
|
||||||
|
self.preferences_group.add(self.status_row)
|
||||||
|
|
||||||
|
self.shortcut_preferences_group = Adw.PreferencesGroup()
|
||||||
|
self.shortcut_preferences_group.set_title("Shortcuts")
|
||||||
|
self.preferences_page.add(self.shortcut_preferences_group)
|
||||||
|
|
||||||
|
self.autofill_row = Adw.ActionRow()
|
||||||
|
self.autofill_row.set_title("Autofill Shortcut")
|
||||||
|
self.autofill_row.set_subtitle("Unavailable, please set up a shortcut in your desktop environment (README)")
|
||||||
|
self.shortcut_preferences_group.add(self.autofill_row)
|
||||||
|
|
||||||
|
self.copy_username_shortcut_row = Adw.ActionRow()
|
||||||
|
self.copy_username_shortcut_row.set_title("Copy Username Shortcut")
|
||||||
|
self.copy_username_shortcut_row.set_subtitle("U")
|
||||||
|
self.shortcut_preferences_group.add(self.copy_username_shortcut_row)
|
||||||
|
|
||||||
|
self.copy_password_shortcut_row = Adw.ActionRow()
|
||||||
|
self.copy_password_shortcut_row.set_title("Copy Password Shortcut")
|
||||||
|
self.copy_password_shortcut_row.set_subtitle("P")
|
||||||
|
self.shortcut_preferences_group.add(self.copy_password_shortcut_row)
|
||||||
|
|
||||||
|
self.vault_status_preferences_group = Adw.PreferencesGroup()
|
||||||
|
self.vault_status_preferences_group.set_title("Vault Status")
|
||||||
|
self.preferences_page.add(self.vault_status_preferences_group)
|
||||||
|
|
||||||
|
self.status_row = Adw.ActionRow()
|
||||||
|
self.status_row.set_title("Vault Status")
|
||||||
|
self.status_row.set_subtitle("Locked")
|
||||||
|
self.vault_status_preferences_group.add(self.status_row)
|
||||||
|
|
||||||
|
self.login_row = Adw.ActionRow()
|
||||||
|
self.login_row.set_title("Vault Login Entries")
|
||||||
|
self.login_row.set_subtitle("0")
|
||||||
|
self.vault_status_preferences_group.add(self.login_row)
|
||||||
|
|
||||||
|
self.notes_row = Adw.ActionRow()
|
||||||
|
self.notes_row.set_title("Vault Notes")
|
||||||
|
self.notes_row.set_subtitle("0")
|
||||||
|
self.vault_status_preferences_group.add(self.notes_row)
|
||||||
|
|
||||||
|
self.action_preferences_group = Adw.PreferencesGroup()
|
||||||
|
self.action_preferences_group.set_title("Actions")
|
||||||
|
self.preferences_page.add(self.action_preferences_group)
|
||||||
|
|
||||||
|
self.login_button = Gtk.Button()
|
||||||
|
self.login_button.set_label("Login")
|
||||||
|
self.login_button.connect("clicked", lambda button: show_login())
|
||||||
|
self.login_button.set_sensitive(False)
|
||||||
|
self.login_button.set_margin_top(10)
|
||||||
|
self.login_button.get_style_context().add_class("suggested-action")
|
||||||
|
self.action_preferences_group.add(self.login_button)
|
||||||
|
|
||||||
|
self.set_pin_button = Gtk.Button()
|
||||||
|
self.set_pin_button.set_label("Set Pin")
|
||||||
|
def set_pin():
|
||||||
|
set_pin_thread = Thread(target=goldwarden.enable_pin)
|
||||||
|
set_pin_thread.start()
|
||||||
|
self.set_pin_button.connect("clicked", lambda button: set_pin())
|
||||||
|
self.set_pin_button.set_margin_top(10)
|
||||||
|
self.set_pin_button.set_sensitive(False)
|
||||||
|
self.set_pin_button.get_style_context().add_class("suggested-action")
|
||||||
|
self.action_preferences_group.add(self.set_pin_button)
|
||||||
|
|
||||||
|
self.unlock_button = Gtk.Button()
|
||||||
|
self.unlock_button.set_label("Unlock")
|
||||||
|
self.unlock_button.set_margin_top(10)
|
||||||
|
def unlock_button_clicked():
|
||||||
|
action = goldwarden.unlock if self.unlock_button.get_label() == "Unlock" else goldwarden.lock
|
||||||
|
unlock_thread = Thread(target=action)
|
||||||
|
unlock_thread.start()
|
||||||
|
self.unlock_button.connect("clicked", lambda button: unlock_button_clicked())
|
||||||
|
# set disabled
|
||||||
|
self.unlock_button.set_sensitive(False)
|
||||||
|
self.action_preferences_group.add(self.unlock_button)
|
||||||
|
|
||||||
|
self.logout_button = Gtk.Button()
|
||||||
|
self.logout_button.set_label("Logout")
|
||||||
|
self.logout_button.set_margin_top(10)
|
||||||
|
self.logout_button.connect("clicked", lambda button: goldwarden.purge())
|
||||||
|
self.logout_button.get_style_context().add_class("destructive-action")
|
||||||
|
self.action_preferences_group.add(self.logout_button)
|
||||||
|
|
||||||
|
def update_labels():
|
||||||
|
pin_set = goldwarden.is_pin_enabled()
|
||||||
|
status = goldwarden.get_vault_status()
|
||||||
|
if status != None:
|
||||||
|
locked = status["locked"]
|
||||||
|
self.login_button.set_sensitive(pin_set and not locked)
|
||||||
|
self.set_pin_button.set_sensitive(not pin_set or not locked)
|
||||||
|
self.status_row.set_subtitle(str("Unlocked" if not locked else "Locked"))
|
||||||
|
self.login_row.set_subtitle(str(status["loginEntries"]))
|
||||||
|
self.notes_row.set_subtitle(str(status["noteEntries"]))
|
||||||
|
self.unlock_button.set_sensitive(True)
|
||||||
|
self.unlock_button.set_label("Unlock" if locked else "Lock")
|
||||||
|
else:
|
||||||
|
is_daemon_running = goldwarden.is_daemon_running()
|
||||||
|
if not is_daemon_running:
|
||||||
|
self.status_row.set_subtitle("Daemon not running")
|
||||||
|
GLib.timeout_add(1000, update_labels)
|
||||||
|
|
||||||
|
GLib.timeout_add(1000, update_labels)
|
||||||
|
self.set_default_size(400, 700)
|
||||||
|
self.set_title("Goldwarden")
|
||||||
|
|
||||||
|
|
||||||
|
#add title buttons
|
||||||
|
self.title_bar = Gtk.HeaderBar()
|
||||||
|
self.set_titlebar(self.title_bar)
|
||||||
|
|
||||||
|
class MyApp(Adw.Application):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.connect('activate', self.on_activate)
|
||||||
|
|
||||||
|
def on_activate(self, app):
|
||||||
|
self.settings_win = SettingsWinvdow(application=app)
|
||||||
|
self.settings_win.present()
|
||||||
|
|
||||||
|
app = MyApp(application_id="com.quexten.Goldwarden")
|
||||||
|
|
||||||
|
def show_login():
|
||||||
|
dialog = Gtk.Dialog(title="Goldwarden")
|
||||||
|
preference_group = Adw.PreferencesGroup()
|
||||||
|
preference_group.set_title("Config")
|
||||||
|
dialog.get_content_area().append(preference_group)
|
||||||
|
|
||||||
|
api_url_entry = Adw.EntryRow()
|
||||||
|
api_url_entry.set_title("API Url")
|
||||||
|
# set value
|
||||||
|
api_url_entry.set_text("https://api.bitwarden.com/")
|
||||||
|
preference_group.add(api_url_entry)
|
||||||
|
|
||||||
|
identity_url_entry = Adw.EntryRow()
|
||||||
|
identity_url_entry.set_title("Identity Url")
|
||||||
|
identity_url_entry.set_text("https://identity.bitwarden.com/")
|
||||||
|
preference_group.add(identity_url_entry)
|
||||||
|
|
||||||
|
notification_url_entry = Adw.EntryRow()
|
||||||
|
notification_url_entry.set_title("Notification URL")
|
||||||
|
notification_url_entry.set_text("https://notifications.bitwarden.com/")
|
||||||
|
preference_group.add(notification_url_entry)
|
||||||
|
|
||||||
|
auth_preference_group = Adw.PreferencesGroup()
|
||||||
|
auth_preference_group.set_title("Authentication")
|
||||||
|
dialog.get_content_area().append(auth_preference_group)
|
||||||
|
|
||||||
|
email_entry = Adw.EntryRow()
|
||||||
|
email_entry.set_title("Email")
|
||||||
|
email_entry.set_text("")
|
||||||
|
auth_preference_group.add(email_entry)
|
||||||
|
|
||||||
|
dialog.add_button("Login", Gtk.ResponseType.OK)
|
||||||
|
def on_save(res):
|
||||||
|
if res != Gtk.ResponseType.OK:
|
||||||
|
return
|
||||||
|
goldwarden.set_api_url(api_url_entry.get_text())
|
||||||
|
goldwarden.set_identity_url(identity_url_entry.get_text())
|
||||||
|
goldwarden.set_notification_url(notification_url_entry.get_text())
|
||||||
|
def login():
|
||||||
|
res = goldwarden.login_with_password(email_entry.get_text(), "password")
|
||||||
|
def handle_res():
|
||||||
|
print("handle res", res)
|
||||||
|
if res == "ok":
|
||||||
|
dialog.close()
|
||||||
|
print("ok")
|
||||||
|
elif res == "badpass":
|
||||||
|
bad_pass_diag = Gtk.MessageDialog(transient_for=dialog, modal=True, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text="Bad password")
|
||||||
|
bad_pass_diag.connect("response", lambda dialog, response: bad_pass_diag.close())
|
||||||
|
bad_pass_diag.present()
|
||||||
|
GLib.idle_add(handle_res)
|
||||||
|
|
||||||
|
login_thread = Thread(target=login)
|
||||||
|
login_thread.start()
|
||||||
|
|
||||||
|
#ok response
|
||||||
|
dialog.connect("response", lambda dialog, response: on_save(response))
|
||||||
|
dialog.set_default_size(400, 200)
|
||||||
|
dialog.set_modal(True)
|
||||||
|
dialog.present()
|
||||||
|
|
||||||
|
app.run(sys.argv)
|
||||||
Loading…
Reference in a new issue