diff --git a/.github/com.quexten.Goldwarden.yml b/.github/com.quexten.Goldwarden.yml index 67bdcc4..f15c81a 100644 --- a/.github/com.quexten.Goldwarden.yml +++ b/.github/com.quexten.Goldwarden.yml @@ -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 ./goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg + - install -D ./com.quexten.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: diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index 05fdf3c..3c088e1 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -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@v3 + - uses: actions/upload-artifact@v4 with: name: goldwarden path: ./goldwarden @@ -37,7 +37,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Download daemon - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: goldwarden - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 diff --git a/Readme.md b/Readme.md index 16e5140..7dab9f2 100644 --- a/Readme.md +++ b/Readme.md @@ -1,4 +1,4 @@ - + # Goldwarden diff --git a/cli/agent/bitwarden/http.go b/cli/agent/bitwarden/http.go index 3fef11d..cdaefc3 100644 --- a/cli/agent/bitwarden/http.go +++ b/cli/agent/bitwarden/http.go @@ -98,6 +98,9 @@ func makeAuthenticatedHTTPRequest(ctx context.Context, req *http.Request, recv i req.Header.Set("Authorization", "Bearer "+token) } req.Header.Set("device-type", deviceType()) + req.Header.Set("User-Agent", "Goldwarden (github.com/quexten/goldwarden)") + req.Header.Set("Bitwarden-Client-Name", "goldwarden") + req.Header.Set("Bitwarden-Client-Version", "0.0.0") res, err := httpClient.Do(req) if err != nil { diff --git a/cli/agent/bitwarden/models/models.go b/cli/agent/bitwarden/models/models.go index 5eb58f8..11ff9a2 100644 --- a/cli/agent/bitwarden/models/models.go +++ b/cli/agent/bitwarden/models/models.go @@ -76,7 +76,8 @@ type Cipher struct { 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"` } @@ -88,8 +89,15 @@ 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"` diff --git a/cli/agent/bitwarden/sync.go b/cli/agent/bitwarden/sync.go index 44e77ef..6ad5a5f 100644 --- a/cli/agent/bitwarden/sync.go +++ b/cli/agent/bitwarden/sync.go @@ -59,6 +59,8 @@ 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) } } diff --git a/cli/agent/systemauth/pinentry/go-pinentry.go b/cli/agent/systemauth/pinentry/go-pinentry.go index 648a7e3..67b4d99 100644 --- a/cli/agent/systemauth/pinentry/go-pinentry.go +++ b/cli/agent/systemauth/pinentry/go-pinentry.go @@ -9,11 +9,16 @@ import ( "github.com/twpayne/go-pinentry" ) -func getPassword(title string, description string) (string, error) { +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, @@ -49,8 +54,10 @@ func getApproval(title string, description string) (bool, error) { return true, nil } + binaryClientOption := getBinaryClientOption() + client, err := pinentry.NewClient( - pinentry.WithBinaryNameFromGnuPGAgentConf(), + binaryClientOption, pinentry.WithGPGTTY(), pinentry.WithTitle(title), pinentry.WithDesc(description), diff --git a/cli/agent/systemauth/systemauth.go b/cli/agent/systemauth/systemauth.go index eb0c868..0f2964e 100644 --- a/cli/agent/systemauth/systemauth.go +++ b/cli/agent/systemauth/systemauth.go @@ -55,9 +55,12 @@ 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.ParentPid == ctx.ParentProcessPid && session.GrandParentPid == ctx.GrandParentProcessPid && session.sessionType == sessionType { - if session.Expires.After(time.Now()) { - return true + 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 + } } } } diff --git a/cli/agent/vault/vault.go b/cli/agent/vault/vault.go index 0db0f38..1a86b2f 100644 --- a/cli/agent/vault/vault.go +++ b/cli/agent/vault/vault.go @@ -3,6 +3,8 @@ package vault import ( "errors" "strings" + "fmt" + "regexp" "sync" "github.com/quexten/goldwarden/cli/agent/bitwarden/crypto" @@ -17,6 +19,7 @@ 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 @@ -29,6 +32,7 @@ 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, @@ -90,6 +94,12 @@ 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 @@ -174,6 +184,26 @@ 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() @@ -211,11 +241,19 @@ func (vault *Vault) GetSSHKeys() []SSHKey { } } - privateKey = strings.Replace(privateKey, "-----BEGIN OPENSSH PRIVATE KEY-----", "", 1) - privateKey = strings.Replace(privateKey, "-----END OPENSSH PRIVATE KEY-----", "", 1) + 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 + } pkParts := strings.Join(strings.Split(privateKey, " "), "\n") - privateKeyString := "-----BEGIN OPENSSH PRIVATE KEY-----" + pkParts + "-----END OPENSSH PRIVATE KEY-----" + privateKeyString := beginMarker + pkParts + endMarker decryptedTitle, err := crypto.DecryptWith(vault.secureNotes[id].Name, key) if err != nil { @@ -228,6 +266,20 @@ 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 } diff --git a/go.mod b/go.mod index ddbeca0..0371926 100644 --- a/go.mod +++ b/go.mod @@ -14,20 +14,20 @@ require ( 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.0.0-20230924165045-adcb03233bb5 + github.com/icza/gox v0.2.0 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.3 + github.com/rymdport/portal v0.2.6 github.com/spf13/cobra v1.8.1 github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 github.com/tink-crypto/tink-go/v2 v2.2.0 github.com/twpayne/go-pinentry v0.3.0 github.com/vmihailenco/msgpack/v5 v5.4.1 - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.28.0 golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f - golang.org/x/sys v0.22.0 + golang.org/x/sys v0.26.0 ) require ( diff --git a/go.sum b/go.sum index 0b0be16..538ec64 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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.0.0-20230924165045-adcb03233bb5 h1:K7KEFpKgVcjj98jOu2Z3xMBTtTwfYVT90Zmo3ZuWmbE= -github.com/icza/gox v0.0.0-20230924165045-adcb03233bb5/go.mod h1:VbcN86fRkkUMPX2ufM85Um8zFndLZswoIW1eYtpAcVk= +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/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= @@ -84,8 +84,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.3 h1:5RoAuMy5wNzEzITwK+9YpMQLU5m7F7IYfmPwN/aVpUk= -github.com/rymdport/portal v0.2.3/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= +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/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -106,8 +106,8 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU 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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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,10 +118,10 @@ 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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/gui/com.quexten.Goldwarden.Devel.yml b/gui/com.quexten.Goldwarden.Devel.yml index 55d5991..39e5618 100644 --- a/gui/com.quexten.Goldwarden.Devel.yml +++ b/gui/com.quexten.Goldwarden.Devel.yml @@ -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/goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg + - install -D ./gui/com.quexten.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: diff --git a/gui/goldwarden.svg b/gui/com.quexten.Goldwarden.svg similarity index 100% rename from gui/goldwarden.svg rename to gui/com.quexten.Goldwarden.svg