Initial import

This commit is contained in:
2025-09-04 14:14:02 +02:00
commit ac609a54c2
19 changed files with 1228 additions and 0 deletions

177
keyring_unix.go Normal file
View 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
}