Initial import
This commit is contained in:
177
keyring_unix.go
Normal file
177
keyring_unix.go
Normal file
@@ -0,0 +1,177 @@
|
||||
//go:build (dragonfly && cgo) || (freebsd && cgo) || linux || netbsd || openbsd
|
||||
|
||||
package secret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
dbusServiceName = "org.freedesktop.secrets"
|
||||
dbusServicePath = "/org/freedesktop/secrets"
|
||||
dbusServiceInterface = "org.freedesktop.Secret.Service"
|
||||
dbusServiceClose = dbusServiceInterface + ".Close"
|
||||
dbusServiceUnlock = dbusServiceInterface + ".Unlock"
|
||||
dbusCollectionBasePath = "/org/freedesktop/secrets/collection/"
|
||||
dbusCollectionLoginPath = dbusCollectionBasePath + "login"
|
||||
dbusCollectionLoginAliasPath = "/org/freedesktop/secrets/aliases/default"
|
||||
dbusCollectionInterface = "org.freedesktop.Secret.Collection"
|
||||
dbusCollectionGetSecret = dbusCollectionInterface + ".GetSecret"
|
||||
dbusCollectionSearchItems = dbusCollectionInterface + ".SearchItems"
|
||||
dbusPromptInterface = "org.freedesktop.Secret.Prompt"
|
||||
dbusPrompt = dbusPromptInterface + ".Prompt"
|
||||
dbusPromptCompleted = dbusPromptInterface + ".Completed"
|
||||
)
|
||||
|
||||
type keyring struct {
|
||||
service string
|
||||
session *dbus.Conn
|
||||
object dbus.BusObject
|
||||
}
|
||||
|
||||
type keyringSecret struct {
|
||||
Session dbus.ObjectPath
|
||||
Parameters []byte
|
||||
Value []byte
|
||||
ContentType string `dbus:"content_type"`
|
||||
}
|
||||
|
||||
func keyringProvider(service string) (Provider, error) {
|
||||
session, err := dbus.SessionBus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &keyring{
|
||||
service: service,
|
||||
session: session,
|
||||
object: session.Object(dbusServiceName, dbusServicePath),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *keyring) Close() error {
|
||||
return p.object.Call(dbusServiceClose, 0).Err
|
||||
}
|
||||
|
||||
func (p *keyring) GetSecret(key string) (value []byte, err error) {
|
||||
|
||||
collection := p.getLoginCollection()
|
||||
if err = p.unlock(collection.Path()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
query := map[string]string{
|
||||
"service": p.service,
|
||||
"username": key,
|
||||
}
|
||||
|
||||
var results []dbus.ObjectPath
|
||||
if results, err = p.search(collection, query); err != nil {
|
||||
return
|
||||
} else if len(results) == 0 {
|
||||
return nil, NotFound{Key: key}
|
||||
}
|
||||
|
||||
var secret *keyringSecret
|
||||
if secret, err = p.getSecret(results[0]); err != nil {
|
||||
return
|
||||
}
|
||||
return secret.Value, nil
|
||||
}
|
||||
|
||||
func (p keyring) checkCollectionPath(here dbus.ObjectPath) bool {
|
||||
obj := p.session.Object(dbusServiceName, dbusServicePath)
|
||||
val, err := obj.GetProperty(dbusCollectionInterface)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
paths := val.Value().([]dbus.ObjectPath)
|
||||
for _, p := range paths {
|
||||
if p == here {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p keyring) getLoginCollection() dbus.BusObject {
|
||||
here := dbus.ObjectPath(dbusCollectionLoginPath)
|
||||
if !p.checkCollectionPath(here) {
|
||||
here = dbus.ObjectPath(dbusCollectionLoginAliasPath)
|
||||
}
|
||||
return p.session.Object(dbusServiceName, here)
|
||||
}
|
||||
|
||||
func (p *keyring) search(collection dbus.BusObject, query interface{}) (results []dbus.ObjectPath, err error) {
|
||||
err = collection.Call(dbusCollectionSearchItems, 0, query).Store(&results)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *keyring) getSecret(here dbus.ObjectPath) (secret *keyringSecret, err error) {
|
||||
secret = new(keyringSecret)
|
||||
err = p.session.Object(dbusServiceName, here).Call(dbusCollectionGetSecret, 0).Store(secret)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *keyring) unlock(here dbus.ObjectPath) (err error) {
|
||||
var (
|
||||
unlocked []dbus.ObjectPath
|
||||
prompt dbus.ObjectPath
|
||||
)
|
||||
if err = p.object.Call(dbusServiceUnlock, 0, []dbus.ObjectPath{here}).Store(&unlocked, &prompt); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var variant dbus.Variant
|
||||
if _, variant, err = p.handlePrompt(prompt); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
collections := variant.Value()
|
||||
switch t := collections.(type) {
|
||||
case []dbus.ObjectPath:
|
||||
unlocked = append(unlocked, t...)
|
||||
}
|
||||
|
||||
if len(unlocked) != 1 || (here != dbusCollectionLoginAliasPath && unlocked[0] != here) {
|
||||
return fmt.Errorf("secret: failed to unlock keyring collection %q", here)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *keyring) handlePrompt(prompt dbus.ObjectPath) (bool, dbus.Variant, error) {
|
||||
if prompt != dbus.ObjectPath("/") {
|
||||
err := s.session.AddMatchSignal(dbus.WithMatchObjectPath(prompt),
|
||||
dbus.WithMatchInterface(dbusPromptInterface),
|
||||
)
|
||||
if err != nil {
|
||||
return false, dbus.MakeVariant(""), err
|
||||
}
|
||||
|
||||
defer func(s *keyring, options ...dbus.MatchOption) {
|
||||
_ = s.session.RemoveMatchSignal(options...)
|
||||
}(s, dbus.WithMatchObjectPath(prompt), dbus.WithMatchInterface(dbusPromptInterface))
|
||||
|
||||
promptSignal := make(chan *dbus.Signal, 1)
|
||||
s.session.Signal(promptSignal)
|
||||
|
||||
err = s.session.Object(dbusServiceName, prompt).Call(dbusPrompt, 0, "").Err
|
||||
if err != nil {
|
||||
return false, dbus.MakeVariant(""), err
|
||||
}
|
||||
|
||||
signal := <-promptSignal
|
||||
switch signal.Name {
|
||||
case dbusPromptCompleted:
|
||||
dismissed := signal.Body[0].(bool)
|
||||
result := signal.Body[1].(dbus.Variant)
|
||||
return dismissed, result, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false, dbus.MakeVariant(""), nil
|
||||
}
|
Reference in New Issue
Block a user