forked from Silverfish/proton-bridge
GODT-1525: Add keybase/go-keychain/secretservice as new keychain helper.
This commit is contained in:
4
go.mod
4
go.mod
@ -43,12 +43,13 @@ require (
|
|||||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
github.com/getsentry/sentry-go v0.12.0
|
github.com/getsentry/sentry-go v0.12.0
|
||||||
github.com/go-resty/resty/v2 v2.6.0
|
github.com/go-resty/resty/v2 v2.6.0
|
||||||
|
github.com/godbus/dbus v4.1.0+incompatible
|
||||||
github.com/golang/mock v1.4.4
|
github.com/golang/mock v1.4.4
|
||||||
github.com/google/go-cmp v0.5.5
|
github.com/google/go-cmp v0.5.5
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
github.com/hashicorp/go-multierror v1.1.0
|
github.com/hashicorp/go-multierror v1.1.0
|
||||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
|
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
|
||||||
github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621
|
github.com/keybase/go-keychain v0.0.0
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
@ -79,4 +80,5 @@ replace (
|
|||||||
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac
|
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac
|
||||||
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753
|
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753
|
||||||
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57
|
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57
|
||||||
|
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe
|
||||||
)
|
)
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -102,6 +102,8 @@ github.com/cucumber/godog v0.12.1/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6T
|
|||||||
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||||
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
|
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
|
||||||
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||||
|
github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe h1:KRj3wdvA9yE92prNmOjS7x5DOqoyjxqdE30qnrmTasc=
|
||||||
|
github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe/go.mod h1:ZoZU1fnBy3mOLWr3Pg+Y2+nTKtu6ypDte2kZg9HvSwY=
|
||||||
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
|
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
|
||||||
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
||||||
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=
|
||||||
@ -164,6 +166,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
|||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||||
|
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||||
|
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
|||||||
220
pkg/keychain/helper_dbus_linux.go
Normal file
220
pkg/keychain/helper_dbus_linux.go
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
// Copyright (c) 2022 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package keychain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker-credential-helpers/credentials"
|
||||||
|
"github.com/godbus/dbus"
|
||||||
|
"github.com/keybase/go-keychain/secretservice"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
serverAtt = "server"
|
||||||
|
labelAtt = "label"
|
||||||
|
usernameAtt = "username"
|
||||||
|
|
||||||
|
defaulDomain = "protonmail/bridge/users/"
|
||||||
|
defaultLabel = "Docker Credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getSession() (*secretservice.SecretService, *secretservice.Session, error) {
|
||||||
|
service, err := secretservice.NewService()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := service.OpenSession(secretservice.AuthenticationDHAES)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return service, session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTimeout(f func() error) error {
|
||||||
|
err := f()
|
||||||
|
if err == secretservice.ErrPromptTimedOut {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getItems(service *secretservice.SecretService, attributes map[string]string) ([]dbus.ObjectPath, error) {
|
||||||
|
if err := unlock(service); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var items []dbus.ObjectPath
|
||||||
|
err := handleTimeout(func() error {
|
||||||
|
var err error
|
||||||
|
items, err = service.SearchCollection(
|
||||||
|
secretservice.DefaultCollection,
|
||||||
|
attributes,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlock(service *secretservice.SecretService) error {
|
||||||
|
return handleTimeout(func() error {
|
||||||
|
return service.Unlock([]dbus.ObjectPath{secretservice.DefaultCollection})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecretServiceDBusHelper is wrapper around keybase/go-keychain/secretservice
|
||||||
|
// library.
|
||||||
|
type SecretServiceDBusHelper struct{}
|
||||||
|
|
||||||
|
// Add appends credentials to the store.
|
||||||
|
func (s *SecretServiceDBusHelper) Add(creds *credentials.Credentials) error {
|
||||||
|
service, session, err := getSession()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer service.CloseSession(session)
|
||||||
|
|
||||||
|
if err := unlock(service); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := session.NewSecret([]byte(creds.Secret))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes := map[string]string{
|
||||||
|
usernameAtt: creds.Username,
|
||||||
|
serverAtt: creds.ServerURL,
|
||||||
|
labelAtt: defaultLabel,
|
||||||
|
"xdg:schema": "io.docker.Credentials",
|
||||||
|
"docker_cli": "1",
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleTimeout(func() error {
|
||||||
|
_, err = service.CreateItem(
|
||||||
|
secretservice.DefaultCollection,
|
||||||
|
secretservice.NewSecretProperties(creds.ServerURL, attributes),
|
||||||
|
secret,
|
||||||
|
secretservice.ReplaceBehaviorReplace,
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes credentials from the store.
|
||||||
|
func (s *SecretServiceDBusHelper) Delete(serverURL string) error {
|
||||||
|
service, session, err := getSession()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer service.CloseSession(session)
|
||||||
|
|
||||||
|
items, err := getItems(service, map[string]string{
|
||||||
|
labelAtt: defaultLabel,
|
||||||
|
serverAtt: serverURL,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(items) == 0 || err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleTimeout(func() error {
|
||||||
|
return service.DeleteItem(items[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves credentials from the store.
|
||||||
|
// It returns username and secret as strings.
|
||||||
|
func (s *SecretServiceDBusHelper) Get(serverURL string) (string, string, error) {
|
||||||
|
service, session, err := getSession()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
defer service.CloseSession(session)
|
||||||
|
|
||||||
|
if err := unlock(service); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := getItems(service, map[string]string{
|
||||||
|
labelAtt: defaultLabel,
|
||||||
|
serverAtt: serverURL,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(items) == 0 || err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
item := items[0]
|
||||||
|
|
||||||
|
attributes, err := service.GetAttributes(item)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var secretPlaintext []byte
|
||||||
|
err = handleTimeout(func() error {
|
||||||
|
var err error
|
||||||
|
secretPlaintext, err = service.GetSecret(item, *session)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes[usernameAtt], string(secretPlaintext), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns the stored serverURLs and their associated usernames.
|
||||||
|
func (s *SecretServiceDBusHelper) List() (map[string]string, error) {
|
||||||
|
userIDByURL := make(map[string]string)
|
||||||
|
|
||||||
|
service, session, err := getSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer service.CloseSession(session)
|
||||||
|
|
||||||
|
items, err := getItems(service, map[string]string{labelAtt: defaultLabel})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, it := range items {
|
||||||
|
attributes, err := service.GetAttributes(it)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(attributes[serverAtt], defaulDomain) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
userIDByURL[attributes[serverAtt]] = attributes[usernameAtt]
|
||||||
|
}
|
||||||
|
|
||||||
|
return userIDByURL, nil
|
||||||
|
}
|
||||||
@ -28,13 +28,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Pass = "pass-app"
|
Pass = "pass-app"
|
||||||
SecretService = "secret-service"
|
SecretService = "secret-service"
|
||||||
|
SecretServiceDBus = "secret-service-dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { // nolint[noinit]
|
func init() { // nolint[noinit]
|
||||||
Helpers = make(map[string]helperConstructor)
|
Helpers = make(map[string]helperConstructor)
|
||||||
|
|
||||||
|
if isUsable(newDBusHelper("")) {
|
||||||
|
Helpers[SecretServiceDBus] = newDBusHelper
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := exec.LookPath("gnome-keyring"); err == nil && isUsable(newSecretServiceHelper("")) {
|
if _, err := exec.LookPath("gnome-keyring"); err == nil && isUsable(newSecretServiceHelper("")) {
|
||||||
Helpers[SecretService] = newSecretServiceHelper
|
Helpers[SecretService] = newSecretServiceHelper
|
||||||
}
|
}
|
||||||
@ -43,6 +48,8 @@ func init() { // nolint[noinit]
|
|||||||
Helpers[Pass] = newPassHelper
|
Helpers[Pass] = newPassHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultHelper = SecretServiceDBus
|
||||||
|
|
||||||
// If Pass is available, use it by default.
|
// If Pass is available, use it by default.
|
||||||
// Otherwise, if SecretService is available, use it by default.
|
// Otherwise, if SecretService is available, use it by default.
|
||||||
if _, ok := Helpers[Pass]; ok {
|
if _, ok := Helpers[Pass]; ok {
|
||||||
@ -52,6 +59,10 @@ func init() { // nolint[noinit]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newDBusHelper(string) (credentials.Helper, error) {
|
||||||
|
return &SecretServiceDBusHelper{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func newPassHelper(string) (credentials.Helper, error) {
|
func newPassHelper(string) (credentials.Helper, error) {
|
||||||
return &pass.Pass{}, nil
|
return &pass.Pass{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user