Compare commits

..

No commits in common. "main" and "fix/client-credentials" have entirely different histories.

35 changed files with 186 additions and 561 deletions

View File

@ -40,7 +40,7 @@ modules:
- cp -R ./* /app/bin
- chmod +x /app/bin/goldwarden_ui_main.py
- install -D ./com.quexten.Goldwarden.desktop /app/share/applications/com.quexten.Goldwarden.desktop
- install -D ./com.quexten.Goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg
- install -D ./goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg
- install -Dm644 ./com.quexten.Goldwarden.metainfo.xml -t /app/share/metainfo/
- blueprint-compiler batch-compile /app/bin/src/gui/.templates/ /app/bin/src/gui/ /app/bin/src/gui/*.blp
sources:

View File

@ -19,7 +19,7 @@ jobs:
run: sudo apt-get install -y libfido2-dev
- name: Build
run: go build -o goldwarden -v .
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: goldwarden
path: ./goldwarden
@ -37,7 +37,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Download daemon
uses: actions/download-artifact@v4
uses: actions/download-artifact@v2
with:
name: goldwarden
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6

View File

@ -114,7 +114,7 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}
build_macos_x86_64:
runs-on: macos-13
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v1

View File

@ -1,4 +1,4 @@
<img src="https://raw.githubusercontent.com/quexten/goldwarden/main/gui/com.quexten.Goldwarden.svg" width=200>
<img src="https://raw.githubusercontent.com/quexten/goldwarden/main/gui/goldwarden.svg" width=200>
# Goldwarden

View File

@ -16,24 +16,12 @@ import (
func handleAddSSH(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext *sockets.CallingContext) (response messages.IPCMessage, err error) {
req := messages.ParsePayload(msg).(messages.CreateSSHKeyRequest)
cipher, publicKey, err := ssh.NewSSHKeyCipher(req.Name, vault.Keyring)
if err != nil {
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
Success: false,
Message: err.Error(),
})
return
}
cipher, publicKey := ssh.NewSSHKeyCipher(req.Name, vault.Keyring)
_, err = messages.IPCMessageFromPayload(messages.ActionResponse{
Success: true,
})
if err != nil {
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
Success: false,
Message: err.Error(),
})
return
panic(err)
}
token, err := cfg.GetToken()
@ -68,49 +56,7 @@ func handleListSSH(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vau
return
}
func handleImportSSH(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext *sockets.CallingContext) (response messages.IPCMessage, err error) {
req := messages.ParsePayload(msg).(messages.ImportSSHKeyRequest)
cipher, _, err := ssh.SSHKeyCipherFromKey(req.Name, req.Key, vault.Keyring)
if err != nil {
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
Success: false,
Message: err.Error(),
})
return
}
_, err = messages.IPCMessageFromPayload(messages.ActionResponse{
Success: true,
})
if err != nil {
response, err = messages.IPCMessageFromPayload(messages.ActionResponse{
Success: false,
Message: err.Error(),
})
return
}
token, err := cfg.GetToken()
if err != nil {
actionsLog.Warn(err.Error())
}
ctx := context.WithValue(context.TODO(), bitwarden.AuthToken{}, token.AccessToken)
postedCipher, err := bitwarden.PostCipher(ctx, cipher, cfg)
if err == nil {
vault.AddOrUpdateSecureNote(postedCipher)
} else {
actionsLog.Warn("Error posting ssh key cipher: " + err.Error())
}
response, err = messages.IPCMessageFromPayload(messages.ImportSSHKeyResponse{
Success: true,
})
return
}
func init() {
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.CreateSSHKeyRequest{}), ensureEverything(systemauth.SSHKey, handleAddSSH))
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.GetSSHKeysRequest{}), ensureIsNotLocked(ensureIsLoggedIn(handleListSSH)))
AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.ImportSSHKeyRequest{}), ensureEverything(systemauth.SSHKey, handleImportSSH))
}

View File

@ -256,7 +256,7 @@ func handleVaultStatus(request messages.IPCMessage, cfg *config.Config, vault *v
vaultStatus.NumberOfLogins = len(vault.GetLogins())
vaultStatus.NumberOfNotes = len(vault.GetNotes())
vaultStatus.LastSynced = vault.GetLastSynced()
vaultStatus.WebsocketConnected = vault.IsWebsocketConnected()
vaultStatus.WebsockedConnected = vault.IsWebsocketConnected()
vaultStatus.PinSet = cfg.HasPin()
vaultStatus.LoggedIn = cfg.IsLoggedIn()
response, err = messages.IPCMessageFromPayload(vaultStatus)

View File

@ -116,8 +116,7 @@ func LoginWithApiKey(ctx context.Context, email string, cfg *config.Config, vaul
func LoginWithMasterpassword(ctx context.Context, email string, cfg *config.Config, vault *vault.Vault) (LoginResponseToken, crypto.MasterKey, string, error) {
var preLogin preLoginResponse
fmt.Println("Posting prelogin")
if err := authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/accounts/prelogin", &preLogin, preLoginRequest{
if err := authenticatedHTTPPost(ctx, cfg.ConfigFile.ApiUrl+"/accounts/prelogin", &preLogin, preLoginRequest{
Email: email,
}); err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not pre-login: %v", err), "", 0, func() {})
@ -128,14 +127,12 @@ func LoginWithMasterpassword(ctx context.Context, email string, cfg *config.Conf
var masterKey crypto.MasterKey
var hashedPassword string
fmt.Println("Getting password")
password, err := pinentry.GetPassword("Bitwarden Password", "Enter your Bitwarden password")
if err != nil {
notify.Notify("Goldwarden", fmt.Sprintf("Could not get password: %v", err), "", 0, func() {})
return LoginResponseToken{}, crypto.MasterKey{}, "", err
}
fmt.Println("Deriving master key")
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 {
notify.Notify("Goldwarden", fmt.Sprintf("Could not derive master key: %v", err), "", 0, func() {})
@ -145,14 +142,14 @@ func LoginWithMasterpassword(ctx context.Context, email string, cfg *config.Conf
hashedPassword = b64enc.EncodeToString(pbkdf2.Key(masterKey.GetBytes(), []byte(password), 1, 32, sha256.New))
values = urlValues(
"scope", loginScope,
"client_id", "web",
"deviceType", "10",
"deviceIdentifier", cfg.ConfigFile.DeviceUUID,
"deviceName", "firefox",
"grant_type", "password",
"username", email,
"password", string(hashedPassword),
"scope", loginScope,
"client_id", "connector",
"deviceType", deviceType(),
"deviceName", deviceName,
"deviceIdentifier", cfg.ConfigFile.DeviceUUID,
)
var loginResponseToken LoginResponseToken

View File

@ -51,7 +51,7 @@ func authenticatedHTTPPost(ctx context.Context, urlstr string, recv, send interf
}
req.Header.Set("Content-Type", contentType)
if authEmail != "" {
req.Header.Set("Auth-Email", base64.RawURLEncoding.EncodeToString([]byte(authEmail)))
req.Header.Set("Auth-Email", base64.URLEncoding.EncodeToString([]byte(authEmail)))
}
return makeAuthenticatedHTTPRequest(ctx, req, recv)
}
@ -97,12 +97,7 @@ func makeAuthenticatedHTTPRequest(ctx context.Context, req *http.Request, recv i
if token, ok := ctx.Value(AuthToken{}).(string); ok {
req.Header.Set("Authorization", "Bearer "+token)
}
req.Header.Set("Accept", "*/*")
req.Header.Set("Accept-Language", "en-US,en;q=0.5")
req.Header.Set("User-Agent", "Goldwarden (github.com/quexten/goldwarden)")
req.Header.Set("Device-Type", "10")
req.Header.Set("Bitwarden-Client-Name", "goldwarden")
req.Header.Set("Bitwarden-Client-Version", "0.0.0")
req.Header.Set("device-type", deviceType())
res, err := httpClient.Do(req)
if err != nil {

View File

@ -8,77 +8,74 @@ import (
)
type SyncData struct {
Profile Profile `json:"profile"`
Folders []Folder `json:"folders"`
Ciphers []Cipher `json:"ciphers"`
Profile Profile
Folders []Folder
Ciphers []Cipher
}
type Organization struct {
Object string `json:"object"`
Id uuid.UUID `json:"id"`
Name string `json:"name"`
UseGroups bool `json:"useGroups"`
UseDirectory bool `json:"useDirectory"`
UseEvents bool `json:"useEvents"`
UseTotp bool `json:"useTotp"`
Use2fa bool `json:"use2fa"`
UseApi bool `json:"useApi"`
UsersGetPremium bool `json:"usersGetPremium"`
SelfHost bool `json:"selfHost"`
Seats int `json:"seats"`
MaxCollections int `json:"maxCollections"`
MaxStorageGb int `json:"maxStorageGb"`
Key string `json:"key"`
Status int `json:"status"`
Type int `json:"type"`
Enabled bool `json:"enabled"`
Object string
Id uuid.UUID
Name string
UseGroups bool
UseDirectory bool
UseEvents bool
UseTotp bool
Use2fa bool
UseApi bool
UsersGetPremium bool
SelfHost bool
Seats int
MaxCollections int
MaxStorageGb int
Key string
Status int
Type int
Enabled bool
}
type Profile struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
EmailVerified bool `json:"emailVerified"`
Premium bool `json:"premium"`
MasterPasswordHint string `json:"masterPasswordHint"`
Culture string `json:"culture"`
TwoFactorEnabled bool `json:"twoFactorEnabled"`
Key crypto.EncString `json:"key"`
PrivateKey crypto.EncString `json:"privateKey"`
SecurityStamp string `json:"securityStamp"`
Organizations []Organization `json:"organizations"`
ID uuid.UUID
Name string
Email string
EmailVerified bool
Premium bool
MasterPasswordHint string
Culture string
TwoFactorEnabled bool
Key crypto.EncString
PrivateKey crypto.EncString
SecurityStamp string
Organizations []Organization
}
type Folder struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
RevisionDate time.Time `json:"revisionDate"`
ID uuid.UUID
Name string
RevisionDate time.Time
}
type Cipher struct {
Type CipherType `json:"type,omitempty"`
ID *uuid.UUID `json:"id,omitempty"`
Name crypto.EncString `json:"name,omitempty"`
Edit bool `json:"edit,omitempty"`
RevisionDate time.Time `json:"revisionDate,omitempty"`
DeletedDate time.Time `json:"deletedDate,omitempty"`
Type CipherType
ID *uuid.UUID `json:",omitempty"`
Name crypto.EncString
Edit bool
RevisionDate time.Time
DeletedDate time.Time
FolderID *uuid.UUID `json:"folderId,omitempty"`
OrganizationID *uuid.UUID `json:"organizationId,omitempty"`
Favorite bool `json:"favorite,omitempty"`
Attachments interface{} `json:"attachments,omitempty"`
OrganizationUseTotp bool `json:"organizationUseTotp,omitempty"`
CollectionIDs []string `json:"collectionIds,omitempty"`
Fields []Field `json:"fields,omitempty"`
FolderID *uuid.UUID `json:",omitempty"`
OrganizationID *uuid.UUID `json:",omitempty"`
Favorite bool `json:",omitempty"`
Attachments interface{} `json:",omitempty"`
OrganizationUseTotp bool `json:",omitempty"`
CollectionIDs []string `json:",omitempty"`
Fields []Field `json:",omitempty"`
Card *Card `json:"card,omitempty"`
Identity *Identity `json:"identity,omitempty"`
Login *LoginCipher `json:"login,omitempty"`
Notes *crypto.EncString `json:"notes,omitempty"`
SecureNote *SecureNoteCipher `json:"secureNote,omitempty"`
SSHKey *SSHKeyCipher `json:"sshKey,omitempty"`
Key *crypto.EncString `json:"key,omitempty"`
Card *Card `json:",omitempty"`
Identity *Identity `json:",omitempty"`
Login *LoginCipher `json:",omitempty"`
Notes *crypto.EncString `json:",omitempty"`
SecureNote *SecureNoteCipher `json:",omitempty"`
}
type CipherType int
@ -89,66 +86,59 @@ const (
CipherCard = 3
CipherIdentity = 4
CipherNote = 2
CipherSSHKey = 5
)
type SSHKeyCipher struct {
PrivateKey crypto.EncString `json:"privateKey"`
PublicKey crypto.EncString `json:"publicKey"`
KeyFingerprint crypto.EncString `json:"keyFingerprint"`
}
type Card struct {
CardholderName crypto.EncString `json:"cardholderName"`
Brand crypto.EncString `json:"brand"`
Number crypto.EncString `json:"number"`
ExpMonth crypto.EncString `json:"expMonth"`
ExpYear crypto.EncString `json:"expYear"`
Code crypto.EncString `json:"code"`
CardholderName crypto.EncString
Brand crypto.EncString
Number crypto.EncString
ExpMonth crypto.EncString
ExpYear crypto.EncString
Code crypto.EncString
}
type Identity struct {
Title crypto.EncString `json:"title"`
FirstName crypto.EncString `json:"firstName"`
MiddleName crypto.EncString `json:"middleName"`
LastName crypto.EncString `json:"lastName"`
Title crypto.EncString
FirstName crypto.EncString
MiddleName crypto.EncString
LastName crypto.EncString
Username crypto.EncString `json:"username"`
Company crypto.EncString `json:"company"`
SSN crypto.EncString `json:"ssn"`
PassportNumber crypto.EncString `json:"passportNumber"`
LicenseNumber crypto.EncString `json:"licenseNumber"`
Username crypto.EncString
Company crypto.EncString
SSN crypto.EncString
PassportNumber crypto.EncString
LicenseNumber crypto.EncString
Email crypto.EncString `json:"email"`
Phone crypto.EncString `json:"phone"`
Address1 crypto.EncString `json:"address1"`
Address2 crypto.EncString `json:"address2"`
Address3 crypto.EncString `json:"address3"`
City crypto.EncString `json:"city"`
State crypto.EncString `json:"state"`
PostalCode crypto.EncString `json:"postalCode"`
Country crypto.EncString `json:"country"`
Email crypto.EncString
Phone crypto.EncString
Address1 crypto.EncString
Address2 crypto.EncString
Address3 crypto.EncString
City crypto.EncString
State crypto.EncString
PostalCode crypto.EncString
Country crypto.EncString
}
type FieldType int
type Field struct {
Type FieldType `json:"type,omitempty"`
Name crypto.EncString `json:"name,omitempty"`
Value crypto.EncString `json:"value,omitempty"`
Type FieldType
Name crypto.EncString
Value crypto.EncString
}
type LoginCipher struct {
Password crypto.EncString `json:"password,omitempty"`
URI crypto.EncString `json:"uri,omitempty"`
URIs []URI `json:"uris,omitempty"`
Username crypto.EncString `json:"username,omitempty"`
Totp crypto.EncString `json:"totp,omitempty"`
Password crypto.EncString
URI crypto.EncString
URIs []URI
Username crypto.EncString `json:",omitempty"`
Totp crypto.EncString `json:",omitempty"`
}
type URIMatch int
type URI struct {
URI string `json:"uri,omitempty"`
Match URIMatch `json:"match,omitempty"`
URI string
Match URIMatch
}
type SecureNoteType int
@ -157,26 +147,8 @@ type SecureNoteCipher struct {
}
func (cipher Cipher) GetKeyForCipher(keyring crypto.Keyring) (crypto.SymmetricEncryptionKey, error) {
var key1 crypto.SymmetricEncryptionKey = nil
var err error
if cipher.OrganizationID != nil {
key1, err = keyring.GetSymmetricKeyForOrganization(cipher.OrganizationID.String())
} else {
key1, err = keyring.GetAccountKey(), nil
}
if err != nil {
return nil, err
}
if cipher.Key == nil {
return key1, nil
} else {
key, err := crypto.DecryptWith(*cipher.Key, key1)
if err != nil {
return nil, err
} else {
return crypto.MemorySymmetricEncryptionKeyFromBytes(key)
}
return keyring.GetSymmetricKeyForOrganization(cipher.OrganizationID.String())
}
return keyring.GetAccountKey(), nil
}

View File

@ -59,8 +59,6 @@ func DoFullSync(ctx context.Context, vault *vault.Vault, config *config.Config,
vault.AddOrUpdateLogin(cipher)
case models.CipherNote:
vault.AddOrUpdateSecureNote(cipher)
case models.CipherSSHKey:
vault.AddOrUpdateSSHKey(cipher)
}
}

View File

@ -255,11 +255,12 @@ func parseMessageTypeFromMessagePack(messagePack []byte) (int8, string, bool) {
return 0, "", false
}
if len(value) < 5 {
websocketLog.Warn("Invalid message received, length too short")
return 0, "", false
}
value, success := value[4].([]interface{})
if len(value) < 1 || !success {
websocketLog.Warn("Invalid message received, value length less than 1")
websocketLog.Warn("Invalid message received, length too short")
return 0, "", false
}
value1, success := value[0].(map[string]interface{})

View File

@ -89,8 +89,8 @@ func DefaultConfig(useMemguard bool) Config {
useMemguard,
&keyBuffer,
ConfigFile{
IdentityUrl: "https://identity.bitwarden.com",
ApiUrl: "https://api.bitwarden.com",
IdentityUrl: "https://vault.bitwarden.com/identity",
ApiUrl: "https://vault.bitwarden.com/api",
NotificationsUrl: "https://notifications.bitwarden.com",
VaultUrl: "https://vault.bitwarden.com",
EncryptedClientID: "",

View File

@ -12,55 +12,8 @@ import (
"golang.org/x/crypto/ssh"
)
// todo refactor to share code
func SSHKeyCipherFromKey(name string, privateKey string, keyring *crypto.Keyring) (models.Cipher, string, error) {
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return models.Cipher{}, "", err
}
func NewSSHKeyCipher(name string, keyring *crypto.Keyring) (models.Cipher, string) {
pubKey := signer.PublicKey()
encryptedName, _ := crypto.EncryptWith([]byte(name), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedPublicKeyKey, _ := crypto.EncryptWith([]byte("public-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedPublicKeyValue, _ := crypto.EncryptWith([]byte(string(ssh.MarshalAuthorizedKey(pubKey))), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedCustomTypeKey, _ := crypto.EncryptWith([]byte("custom-type"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedCustomTypeValue, _ := crypto.EncryptWith([]byte("ssh-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedPrivateKeyKey, _ := crypto.EncryptWith([]byte("private-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
encryptedPrivateKeyValue, _ := crypto.EncryptWith([]byte(privateKey), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey())
cipher := models.Cipher{
Type: models.CipherNote,
Name: encryptedName,
Notes: &encryptedPublicKeyValue,
ID: nil,
Favorite: false,
OrganizationID: nil,
SecureNote: &models.SecureNoteCipher{
Type: 0,
},
Fields: []models.Field{
{
Type: 0,
Name: encryptedCustomTypeKey,
Value: encryptedCustomTypeValue,
},
{
Type: 0,
Name: encryptedPublicKeyKey,
Value: encryptedPublicKeyValue,
},
{
Type: 1,
Name: encryptedPrivateKeyKey,
Value: encryptedPrivateKeyValue,
},
},
}
return cipher, string(ssh.MarshalAuthorizedKey(pubKey)), nil
}
func NewSSHKeyCipher(name string, keyring *crypto.Keyring) (models.Cipher, string, error) {
var reader io.Reader = rand.Reader
pub, priv, err := ed25519.GenerateKey(reader)
@ -119,5 +72,5 @@ func NewSSHKeyCipher(name string, keyring *crypto.Keyring) (models.Cipher, strin
},
}
return cipher, string(ssh.MarshalAuthorizedKey(publicKey)), nil
return cipher, string(ssh.MarshalAuthorizedKey(publicKey))
}

View File

@ -1,27 +1,16 @@
//go:build freebsd || linux || darwin
//go:build freebsd || linux
package pinentry
import (
"errors"
"runtime"
"github.com/twpayne/go-pinentry"
)
func getBinaryClientOption() (clientOption pinentry.ClientOption) {
binaryClientOption := pinentry.WithBinaryNameFromGnuPGAgentConf()
if runtime.GOOS == "darwin" {
binaryClientOption = pinentry.WithBinaryName("pinentry-mac")
}
return binaryClientOption
}
func getPassword(title string, description string) (string, error) {
binaryClientOption := getBinaryClientOption()
client, err := pinentry.NewClient(
binaryClientOption,
pinentry.WithBinaryNameFromGnuPGAgentConf(),
pinentry.WithGPGTTY(),
pinentry.WithTitle(title),
pinentry.WithDesc(description),
@ -54,10 +43,8 @@ func getApproval(title string, description string) (bool, error) {
return true, nil
}
binaryClientOption := getBinaryClientOption()
client, err := pinentry.NewClient(
binaryClientOption,
pinentry.WithBinaryNameFromGnuPGAgentConf(),
pinentry.WithGPGTTY(),
pinentry.WithTitle(title),
pinentry.WithDesc(description),

View File

@ -1,4 +1,4 @@
//go:build windows
//go:build windows || darwin
package pinentry

View File

@ -55,12 +55,9 @@ func (s *SessionStore) CreateSession(pid int, parentpid int, grandparentpid int,
func (s *SessionStore) verifySession(ctx sockets.CallingContext, sessionType SessionType) bool {
for _, session := range s.Store {
if session.sessionType == sessionType {
// only check for ancestor if the session is not a ssh session
if sessionType == SSHKey || (session.ParentPid == ctx.ParentProcessPid && session.GrandParentPid == ctx.GrandParentProcessPid) {
if session.Expires.After(time.Now()) {
return true
}
if session.ParentPid == ctx.ParentProcessPid && session.GrandParentPid == ctx.GrandParentProcessPid && session.sessionType == sessionType {
if session.Expires.After(time.Now()) {
return true
}
}
}

View File

@ -3,8 +3,6 @@ package vault
import (
"errors"
"strings"
"fmt"
"regexp"
"sync"
"github.com/quexten/goldwarden/cli/agent/bitwarden/crypto"
@ -19,11 +17,10 @@ type Vault struct {
Keyring *crypto.Keyring
logins map[string]models.Cipher
secureNotes map[string]models.Cipher
sshKeys map[string]models.Cipher
sshKeyNoteIDs []string
envCredentials map[string]string
lastSynced int64
websocketConnected bool
websockedConnected bool
mu sync.Mutex
}
@ -32,11 +29,10 @@ func NewVault(keyring *crypto.Keyring) *Vault {
Keyring: keyring,
logins: make(map[string]models.Cipher),
secureNotes: make(map[string]models.Cipher),
sshKeys: make(map[string]models.Cipher),
sshKeyNoteIDs: make([]string, 0),
envCredentials: make(map[string]string),
lastSynced: 0,
websocketConnected: false,
websockedConnected: false,
}
}
@ -94,12 +90,6 @@ func (vault *Vault) AddOrUpdateSecureNote(cipher models.Cipher) {
vault.unlockMutex()
}
func (vault *Vault) AddOrUpdateSSHKey(cipher models.Cipher) {
vault.lockMutex()
vault.sshKeys[cipher.ID.String()] = cipher
vault.unlockMutex()
}
func (vault *Vault) isEnv(cipher models.Cipher) (string, bool) {
if cipher.Type != models.CipherNote {
return "", false
@ -184,26 +174,6 @@ type SSHKey struct {
PublicKey string
}
func extractKeyMarker(text, pattern string) (string, string, error) {
re := regexp.MustCompile(pattern)
match := re.FindStringIndex(text)
if match != nil {
// Extract the matched text
extracted := re.FindString(text[match[0]:match[1]])
if match[0] == 0 {
// begin marker
return extracted, text[match[1]:], nil
} else if match[1] == len(strings.TrimRight(text, "\n\r ")) {
// end marker
return extracted, text[:match[0]], nil
}
return "", text, fmt.Errorf("Token found is neither at the beginning nor end: pattern: %s. match idx: %s", pattern, match)
}
return "", text, fmt.Errorf("No match found in pattern %s", pattern)
}
func (vault *Vault) GetSSHKeys() []SSHKey {
vault.lockMutex()
defer vault.unlockMutex()
@ -241,19 +211,11 @@ func (vault *Vault) GetSSHKeys() []SSHKey {
}
}
beginMarker, privateKey, err := extractKeyMarker(privateKey, `-----\w*BEGIN [a-zA-Z ]+\w*-----`)
if err != nil {
vaultLog.Error("Failed for note %s: %s", vault.secureNotes[id].Name, err.Error())
continue
}
endMarker, privateKey, err := extractKeyMarker(privateKey, `-----\w*END [a-zA-Z ]+\w*-----`)
if err != nil {
vaultLog.Error("Failed for note %s: %s", vault.secureNotes[id].Name, err.Error())
continue
}
privateKey = strings.Replace(privateKey, "-----BEGIN OPENSSH PRIVATE KEY-----", "", 1)
privateKey = strings.Replace(privateKey, "-----END OPENSSH PRIVATE KEY-----", "", 1)
pkParts := strings.Join(strings.Split(privateKey, " "), "\n")
privateKeyString := beginMarker + pkParts + endMarker
privateKeyString := "-----BEGIN OPENSSH PRIVATE KEY-----" + pkParts + "-----END OPENSSH PRIVATE KEY-----"
decryptedTitle, err := crypto.DecryptWith(vault.secureNotes[id].Name, key)
if err != nil {
@ -266,20 +228,6 @@ func (vault *Vault) GetSSHKeys() []SSHKey {
PublicKey: string(publicKey),
})
}
for id, _ := range vault.sshKeys {
key, _ := vault.sshKeys[id].GetKeyForCipher(*vault.Keyring)
privKey, _ := crypto.DecryptWith(vault.sshKeys[id].SSHKey.PrivateKey, key)
pubKey, _ := crypto.DecryptWith(vault.sshKeys[id].SSHKey.PublicKey, key)
name, _ := crypto.DecryptWith(vault.sshKeys[id].Name, key)
sshKeys = append(sshKeys, SSHKey{
Name: string(name),
Key: string(privKey),
PublicKey: string(pubKey),
})
}
return sshKeys
}
@ -476,7 +424,7 @@ func (vault *Vault) GetLastSynced() int64 {
func (vault *Vault) SetWebsocketConnected(connected bool) {
vault.lockMutex()
vault.websocketConnected = connected
vault.websockedConnected = connected
vault.unlockMutex()
}
@ -484,5 +432,5 @@ func (vault *Vault) IsWebsocketConnected() bool {
vault.lockMutex()
defer vault.unlockMutex()
return vault.websocketConnected
return vault.websockedConnected
}

View File

@ -1,7 +1,6 @@
package cmd
import (
"bytes"
"fmt"
"os"
@ -19,12 +18,12 @@ var sshCmd = &cobra.Command{
},
}
// sshAddCmd represents the ssh add command
// runCmd represents the run command
var sshAddCmd = &cobra.Command{
Use: "add",
Short: "Creates a new SSH key and adds it to the SSH Agent.",
Long: `Creates a new SSH key and adds it to the SSH Agent.
The key is stored as a secure note. Consult the documentation for more information.`,
Short: "Runs a command with environment variables from your vault",
Long: `Runs a command with environment variables from your vault.
The variables are stored as a secure note. Consult the documentation for more information.`,
Run: func(cmd *cobra.Command, args []string) {
err := loginIfRequired()
if err != nil {
@ -93,64 +92,6 @@ var listSSHCmd = &cobra.Command{
},
}
var importSSHCmd = &cobra.Command{
Use: "import",
Short: "Imports an SSH key into your vault",
Long: `Imports an SSH key into your vault.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
fmt.Println("Error: No filename for SSH key specified")
return
}
filename := args[0]
fmt.Println("Importing SSH key from " + filename)
name, _ := cmd.Flags().GetString("name")
if name == "" {
name = "Imported SSH Key"
}
if _, err := os.Stat(filename); os.IsNotExist(err) {
fmt.Println("Error: File does not exist")
return
}
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error: " + err.Error())
return
}
buf := new(bytes.Buffer)
buf.ReadFrom(file)
key := buf.String()
result, err := commandClient.SendToAgent(messages.ImportSSHKeyRequest{
Key: key,
Name: name,
})
if err != nil {
handleSendToAgentError(err)
return
}
switch result.(type) {
case messages.ImportSSHKeyResponse:
response := result.(messages.ImportSSHKeyResponse)
if response.Success {
fmt.Println("Success")
} else {
fmt.Println("Error: " + response.ErrorMsg)
}
return
case messages.ActionResponse:
fmt.Println("Error: " + result.(messages.ActionResponse).Message)
return
}
},
}
func init() {
rootCmd.AddCommand(sshCmd)
sshCmd.AddCommand(sshAddCmd)
@ -158,6 +99,4 @@ func init() {
_ = sshAddCmd.MarkFlagRequired("name")
sshAddCmd.PersistentFlags().Bool("clipboard", false, "Copy the public key to the clipboard")
sshCmd.AddCommand(listSSHCmd)
importSSHCmd.PersistentFlags().String("name", "", "")
sshCmd.AddCommand(importSSHCmd)
}

View File

@ -114,7 +114,7 @@ var statusCmd = &cobra.Command{
response["loginEntries"] = status.NumberOfLogins
response["noteEntries"] = status.NumberOfNotes
response["lastSynced"] = time.Unix(status.LastSynced, 0).String()
response["websocketConnected"] = status.WebsocketConnected
response["websocketConnected"] = status.WebsockedConnected
response["pinSet"] = status.PinSet
response["loggedIn"] = status.LoggedIn
responseJSON, _ := json.Marshal(response)

View File

@ -17,16 +17,6 @@ type GetSSHKeysResponse struct {
Keys []string
}
type ImportSSHKeyRequest struct {
Key string
Name string
}
type ImportSSHKeyResponse struct {
Success bool
ErrorMsg string
}
func init() {
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req CreateSSHKeyRequest
@ -63,22 +53,4 @@ func init() {
}
return req, nil
}, GetSSHKeysResponse{})
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req ImportSSHKeyRequest
err := json.Unmarshal(payload, &req)
if err != nil {
panic("Unmarshal: " + err.Error())
}
return req, nil
}, ImportSSHKeyRequest{})
registerPayloadParser(func(payload []byte) (interface{}, error) {
var req ImportSSHKeyResponse
err := json.Unmarshal(payload, &req)
if err != nil {
panic("Unmarshal: " + err.Error())
}
return req, nil
}, ImportSSHKeyResponse{})
}

View File

@ -27,7 +27,7 @@ type VaultStatusResponse struct {
NumberOfLogins int
NumberOfNotes int
LastSynced int64
WebsocketConnected bool
WebsockedConnected bool
}
func init() {

16
go.mod
View File

@ -13,21 +13,21 @@ require (
github.com/gen2brain/beeep v0.0.0-20240112042604-c7bb2cd88fea
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/icza/gox v0.2.0
github.com/gorilla/websocket v1.5.1
github.com/icza/gox v0.0.0-20230924165045-adcb03233bb5
github.com/keybase/client/go v0.0.0-20240424154521-52f30ea26cb1
github.com/lox/go-touchid v0.0.0-20170712105233-619cc8e578d0
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
github.com/mitchellh/go-ps v1.0.0
github.com/rymdport/portal v0.2.6
github.com/spf13/cobra v1.8.1
github.com/rymdport/portal v0.2.2
github.com/spf13/cobra v1.8.0
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4
github.com/tink-crypto/tink-go/v2 v2.2.0
github.com/tink-crypto/tink-go/v2 v2.1.0
github.com/twpayne/go-pinentry v0.3.0
github.com/vmihailenco/msgpack/v5 v5.4.1
golang.org/x/crypto v0.28.0
golang.org/x/crypto v0.22.0
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
golang.org/x/sys v0.26.0
golang.org/x/sys v0.19.0
)
require (
@ -56,3 +56,5 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
)
replace github.com/rymdport/portal => github.com/quexten/portal v0.0.0-20240429200240-156297fa11c5

38
go.sum
View File

@ -15,7 +15,7 @@ github.com/awnumar/memcall v0.2.0/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGa
github.com/awnumar/memguard v0.22.5 h1:PH7sbUVERS5DdXh3+mLo8FDcl1eIeVjJVYMnyuYpvuI=
github.com/awnumar/memguard v0.22.5/go.mod h1:+APmZGThMBWjnMlKiSM1X7MVpbIVewen2MTkqWkA/zE=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -32,12 +32,12 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
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/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/icza/gox v0.2.0 h1:+0N8PCt9/QSx+k0dqe/wdlXJNR/haaPsPwrTJTNDeyk=
github.com/icza/gox v0.2.0/go.mod h1:rVecw5Q6POJAWBcXgCZdAtwK/hmoNehxCkAP3sMnOIc=
github.com/icza/gox v0.0.0-20230924165045-adcb03233bb5 h1:K7KEFpKgVcjj98jOu2Z3xMBTtTwfYVT90Zmo3ZuWmbE=
github.com/icza/gox v0.0.0-20230924165045-adcb03233bb5/go.mod h1:VbcN86fRkkUMPX2ufM85Um8zFndLZswoIW1eYtpAcVk=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/keybase/backoff v1.0.1-0.20160517061000-726b63b835ec h1:D6qL2WCnAuxucGbmL+mDW8IKRK1pex+R1fw5rKa9nXc=
@ -76,6 +76,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quexten/portal v0.0.0-20240429200240-156297fa11c5 h1:roVJ7WlvNo3R4NfVLysq3siLrVZgotg/JZkZ1dQEZ5k=
github.com/quexten/portal v0.0.0-20240429200240-156297fa11c5/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM=
github.com/reiver/go-oi v1.0.0/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI=
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl+f5AQ93FMcadG19WkS7MO6TXFOSas=
@ -84,10 +86,8 @@ github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rymdport/portal v0.2.6 h1:HWmU3gORu7vWcpr7VSwUS2Xx1HtJXVcUuTqEZcMEsIg=
github.com/rymdport/portal v0.2.6/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -98,16 +98,16 @@ github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG0
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w=
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU=
github.com/tink-crypto/tink-go/v2 v2.2.0 h1:L2Da0F2Udh2agtKztdr69mV/KpnY3/lGTkMgLTVIXlA=
github.com/tink-crypto/tink-go/v2 v2.2.0/go.mod h1:JJ6PomeNPF3cJpfWC0lgyTES6zpJILkAX0cJNwlS3xU=
github.com/tink-crypto/tink-go/v2 v2.1.0 h1:QXFBguwMwTIaU17EgZpEJWsUSc60b1BAGTzBIoMdmok=
github.com/tink-crypto/tink-go/v2 v2.1.0/go.mod h1:y1TnYFt1i2eZVfx4OGc+C+EMp4CoKWAw2VSEuoicHHI=
github.com/twpayne/go-pinentry v0.3.0 h1:Rr+fEOZXmeItOb4thjeVaBWJKB9Xa/eojolycyF/26c=
github.com/twpayne/go-pinentry v0.3.0/go.mod h1:iOIZD+9np/2V24OdCGos7Y1/xX90wc6VEAZsgb+r9D4=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
@ -118,12 +118,12 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -102,7 +102,7 @@ modules:
- cp -R ./gui/* /app/bin
- chmod +x /app/bin/goldwarden_ui_main.py
- install -D ./gui/com.quexten.Goldwarden.desktop /app/share/applications/com.quexten.Goldwarden.desktop
- install -D ./gui/com.quexten.Goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg
- install -D ./gui/goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg
- install -Dm644 ./gui/com.quexten.Goldwarden.metainfo.xml -t /app/share/metainfo/
- blueprint-compiler batch-compile /app/bin/src/gui/.templates/ /app/bin/src/gui/ /app/bin/src/gui/*.blp
sources:

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

12
gui/goldwarden_ui_main.py Executable file → Normal file
View File

@ -1,12 +1,4 @@
#!/usr/bin/env python3
import src.linux.main as linux_main
import platform
if platform.system() == 'Darwin':
import src.macos.main as macos_main
macos_main.main()
elif platform.system() == 'Linux':
import src.linux.main as linux_main
linux_main.main()
else:
print("Unsupported OS " + platform.system() + "... exiting...")
linux_main.main()

View File

@ -47,45 +47,15 @@ class GoldwardenLoginApp(Adw.Application):
def on_login(self):
email = self.email_row.get_text()
client_id = self.client_id_row.get_text().strip()
client_secret = self.client_secret_row.get_text().strip()
client_id = self.client_id_row.get_text()
client_secret = self.client_secret_row.get_text()
server = self.server_row.get_text()
try:
goldwarden.set_server(server)
except:
print("set server failed")
dialog = Adw.MessageDialog.new(self.window,
"Failed to set server",
"The server you entered is invalid, please try again.",
)
dialog.add_response("ok", "Dismiss")
dialog.present()
return
goldwarden.set_url(server)
if client_id != "":
goldwarden.set_client_id(client_id)
print("set client id result", goldwarden.set_client_id(client_id.strip()))
if client_secret != "":
goldwarden.set_client_secret(client_secret)
try:
goldwarden.login_with_password(email, "")
except Exception as e:
if "errorbadpassword" in str(e):
dialog = Adw.MessageDialog.new(self.window, "Bad Password", "The username or password you entered is incorrect.")
dialog.add_response("ok", "Dismiss")
dialog.present()
return
if "errorcaptcha" in str(e):
dialog = Adw.MessageDialog.new(self.window, "Unusual traffic error", "Traffic is unusual, please set up api client id and client secret.")
dialog.add_response("ok", "Dismiss")
dialog.present()
return
if "errortotp" in str(e):
dialog = Adw.MessageDialog.new(self.window, "TOTP Invalid", "The TOTP code you entered is invalid.")
dialog.add_response("ok", "Dismiss")
dialog.present()
return
print("set client secret result", goldwarden.set_client_secret(client_secret.strip()))
goldwarden.login_with_password(email, "")
self.window.close()
if __name__ == "__main__":

View File

@ -1,10 +1,10 @@
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
# gi.require_version('Notify', '0.7')
gi.require_version('Notify', '0.7')
import gc
import time
from gi.repository import Gtk, Adw, GLib, Gdk
from gi.repository import Gtk, Adw, GLib, Notify, Gdk
from ..services import goldwarden
from ..services.autotype import autotype
from threading import Thread
@ -12,6 +12,7 @@ from .resource_loader import load_template
import sys
import os
from ..services import totp
Notify.init("Goldwarden")
class GoldwardenQuickAccessApp(Adw.Application):
def __init__(self, **kwargs):
@ -68,9 +69,6 @@ class GoldwardenQuickAccessApp(Adw.Application):
auto_type_combo = state & Gdk.ModifierType.CONTROL_MASK and state & Gdk.ModifierType.SHIFT_MASK
copy_combo = state & Gdk.ModifierType.CONTROL_MASK and not state & Gdk.ModifierType.SHIFT_MASK
if not len(self.filtered_logins) > 0:
return
# totp code
if keyval == Gdk.KEY_t or keyval == Gdk.KEY_T:
if self.filtered_logins[self.selected_index]["totp"] == "":
@ -114,8 +112,8 @@ class GoldwardenQuickAccessApp(Adw.Application):
def run_autotype(self, text):
def perform_autotype(text):
GLib.idle_add(self.window.hide)
time.sleep(2)
self.window.hide()
time.sleep(0.1)
autotype.autotype(text)
time.sleep(0.1)
os._exit(0)
@ -147,7 +145,7 @@ class GoldwardenQuickAccessApp(Adw.Application):
self.filtered_logins = self.filtered_logins[0:7]
def render_list(self):
if len(self.filtered_logins) > 0:
if len(self.filtered_logins) > 1:
self.results_list.set_visible(True)
while self.results_list.get_first_child() != None:
self.results_list.remove(self.results_list.get_first_child())

View File

@ -110,7 +110,7 @@ Adw.ApplicationWindow window {
icon-name: "emblem-synchronizing-symbolic";
}
Adw.ActionRow websocket_connected_row {
title: "Websocket Connected";
title: "Websocked Connected";
subtitle: "False";
}
Adw.ActionRow logins_row {

View File

@ -101,7 +101,8 @@ class GoldwardenSettingsApp(Adw.Application):
if status == None:
is_daemon_running = goldwarden.is_daemon_running()
if not is_daemon_running:
print("Daemon not running")
self.status_row.set_subtitle("Daemon not running")
self.vault_status_icon.set_icon("dialog-error", "error")
return
logged_in = status["loggedIn"]

View File

@ -7,7 +7,6 @@ gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import GLib, Gio
import sys
from threading import Timer
def set_status(message):
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
@ -30,13 +29,9 @@ def set_status(message):
sys.exit(0)
except Exception as e:
print(e)
sys.exit(0)
if len(sys.argv) > 1:
set_status(sys.argv[1])
thread = Timer(10, sys.exit, [0])
thread.start()
loop = GLib.MainLoop()
loop.run()

View File

@ -1,30 +0,0 @@
import sys
import subprocess
import os
import secrets
from src.services import goldwarden
from src.services import pinentry
import time
root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir))
def main():
token = secrets.token_hex(32)
if not os.environ.get("GOLDWARDEN_DAEMON_AUTH_TOKEN") == None:
token = os.environ["GOLDWARDEN_DAEMON_AUTH_TOKEN"]
print("Starting Goldwarden GUI")
goldwarden.run_daemon_background(token)
time.sleep(1)
#pinentry.daemonize()
if not "--hidden" in sys.argv:
p = subprocess.Popen(["python3", "-m", "src.gui.settings"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{token}\n".encode())
p.stdin.flush()
# print stdout
while True:
line = p.stderr.readline()
if not line:
break
print(line.decode().strip())
while True:
time.sleep(60)

View File

@ -1,5 +1,5 @@
from ..goldwarden import autotype
def autotype_libportal(text):
def libportal_autotype(text):
print("autotypeing with libportal")
autotype(text)
goldwarden.autotype(text)

View File

@ -2,7 +2,4 @@ import pyautogui
def autotype_pyautogui(text):
print("autotypeing with pyautogui")
pyautogui.write(text, interval=0.02)
if __name__ == "__main__":
autotype_pyautogui("hello world")
pyautogui.write(text, interval=0.02)

View File

@ -70,10 +70,8 @@ def set_notification_url(url):
def set_vault_url(url):
send_authenticated_command(f"config set-vault-url {url}")
def set_server(url):
result = send_authenticated_command(f"config set-server {url}")
if result.strip() != "Done":
raise Exception("Failed to set server")
def set_url(url):
send_authenticated_command(f"config set-url {url}")
def get_environment():
result = send_authenticated_command(f"config get-environment")
@ -90,12 +88,9 @@ def set_client_secret(client_secret):
def login_with_password(email, password):
result = send_authenticated_command(f"vault login --email {email}")
if "Login failed" in result and "username or password" in result.lower():
raise Exception("errorbadpassword")
if "Login failed" in result and ("error code 7" in result.lower() or "error code 6" in result.lower()):
raise Exception("errorcaptcha")
if "Login failed" in result and "two-factor" in result.lower():
raise Exception("errortotp")
if not "Logged in" in result:
return "badpass"
return "ok"
def login_passwordless(email):
send_authenticated_command(f"vault login --email {email} --passwordless")