checking in some work- adding custom errors and low-level compat with SecretService, but it may not be needed.

This commit is contained in:
brent s. 2021-11-28 21:43:30 -05:00
parent cf24035c85
commit 3cab98e584
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
8 changed files with 416 additions and 92 deletions

4
.idea/misc.xml generated
View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GOROOT" path="/usr/lib/go" />
</project>

8
.ref
View File

@ -1 +1,9 @@
https://specifications.freedesktop.org/secret-service/latest/index.html
https://developer-old.gnome.org/libsecret/unstable/
https://developer-old.gnome.org/libsecret/0.18/
https://people.gnome.org/~stefw/libsecret-docs/index.html


Quick reference URLs:

Dbus paths: https://specifications.freedesktop.org/secret-service/latest/ch12.html

104
consts.go
View File

@ -18,16 +18,16 @@ const (

// Methods

// DbusServiceChangeLock is [FUNCTION UNKNOWN; TODO.]
DbusServiceChangeLock string = DbusInterfaceService + ".ChangeLock"
/*
DbusServiceChangeLock has some references in the SecretService Dbus API but
it seems to be obsolete - undocumented, at the least.
*/
// DbusServiceChangeLock string = DbusInterfaceService + ".ChangeLock"

// DbusServiceCreateCollection is used to create a new Collection if it doesn't exist in Dbus.
// DbusServiceCreateCollection is used to create a new Collection via Service.CreateCollection.
DbusServiceCreateCollection string = DbusInterfaceService + ".CreateCollection"

/*
DbusServiceGetSecrets is used to fetch all Secret / Item items in a given Collection
(via Service.GetSecrets).
*/
// DbusServiceGetSecrets is used to fetch all Secret / Item items in a given Collection (via Service.GetSecrets).
DbusServiceGetSecrets string = DbusInterfaceService + ".GetSecrets"

// DbusServiceLock is used by Service.Lock.
@ -128,6 +128,26 @@ const (

// DbusItemSetSecret is used by Item.SetSecret.
DbusItemSetSecret string = DbusInterfaceItem + ".SetSecret"

// Properties

// DbusItemLocked is a Dbus boolean for Item.Locked.
DbusItemLocked string = DbusInterfaceItem + ".Locked"

// DbusItemAttributes contains attributes (metadata, schema, etc.) for Item.Attributes.
DbusItemAttributes string = DbusInterfaceItem + ".Attributes"

// DbusItemLabel is the name (label) for Item.Label.
DbusItemLabel string = DbusInterfaceItem + ".Label"

// DbusItemType is the type of an Item (Item.Type).
DbusItemType string = DbusInterfaceItem + ".Type"

// DbusItemCreated is the time an Item was created (in a UNIX Epoch uint64) for Item.Created.
DbusItemCreated string = DbusInterfaceItem + ".Created"

// DbusItemModified is the time an Item was last modified (in a UNIX Epoch uint64) for Item.Modified.
DbusItemModified string = DbusInterfaceItem + ".Modified"
)

// Dbus paths.
@ -137,3 +157,73 @@ const (
// DbusPromptPrefix is the path used for prompts comparison.
DbusPromptPrefix string = DbusPath + "/prompt/"
)

// FLAGS
// These are not currently used, but may be in the future.

// SERVICE

// ServiceInitFlag is a flag for Service.Open.
type ServiceInitFlag int

const (
FlagServiceNone ServiceInitFlag = iota
FlagServiceOpenSession
FlagServiceLoadCollections
)

// ServiceSearchFlag is a flag for Service.SearchItems.
type ServiceSearchFlag int

const (
FlagServiceSearchNone ServiceSearchFlag = iota
FlagServiceSearchAll
FlagServiceSearchUnlock
FlagServiceSearchLoadSecrets
)

// COLLECTION

// CollectionInitFlag is a flag for Collection.SearchItems and Collection.Items.
type CollectionInitFlag int

const (
FlagCollectionNone CollectionInitFlag = iota
FlagCollectionLoadItems
)

// ITEM

// ItemInitFlag are flags for Collection.SearchItems and Collection.Items.
type ItemInitFlag int

const (
FlagItemNone ItemInitFlag = iota
FlagItemLoadSecret
)

// ItemSearchFlag are flags for Collection.CreateItem.
type ItemSearchFlag int

const (
FlagItemCreateNone ItemSearchFlag = iota
FlatItemCreateReplace
)

// ERRORS

/*
SecretServiceErrEnum are just constants for the enum'd errors;
see SecretServiceError type and ErrSecretService* vars for what
actually gets returned.
They're used for finding the appropriate matching error.
*/
type SecretServiceErrEnum int

const (
EnumErrProtocol SecretServiceErrEnum = iota
EnumErrIsLocked
EnumErrNoSuchObject
EnumErrAlreadyExists
EnumErrInvalidFileFormat
)

56
errs.go
View File

@ -1,10 +1,10 @@
package gosecret

import (
`errors`
"errors"
)

// Errors.
// General errors.
var (
// ErrBadDbusPath indicates an invalid path - either nothing exists at that path or the path is malformed.
ErrBadDbusPath error = errors.New("invalid dbus path")
@ -13,3 +13,55 @@ var (
// ErrNoDbusConn gets triggered if a connection to Dbus can't be detected.
ErrNoDbusConn error = errors.New("no valid dbus connection")
)

/*
Translated SecretService errors.
See https://developer-old.gnome.org/libsecret/unstable/libsecret-SecretError.html#SecretError.
Used by TranslateError.
*/
var (
ErrUnknownSecretServiceErr error = errors.New("cannot find matching SecretService error")
ErrSecretServiceProto SecretServiceError = SecretServiceError{
ErrCode: EnumErrProtocol,
ErrName: "SECRET_ERROR_PROTOCOL",
ErrDesc: "an invalid message or data was received from SecretService",
}
ErrSecretServiceLocked SecretServiceError = SecretServiceError{
ErrCode: EnumErrIsLocked,
ErrName: "SECRET_ERROR_IS_LOCKED",
ErrDesc: "the item/collection is locked; the specified operation cannot be performed",
}
ErrSecretServiceNoObj SecretServiceError = SecretServiceError{
ErrCode: EnumErrNoSuchObject,
ErrName: "SECRET_ERROR_NO_SUCH_OBJECT",
ErrDesc: "no such item/collection was found in SecretService",
}
ErrSecretServiceExists SecretServiceError = SecretServiceError{
ErrCode: EnumErrAlreadyExists,
ErrName: "SECRET_ERROR_ALREADY_EXISTS",
ErrDesc: "a relevant item/collection already exists",
}
ErrSecretServiceInvalidFormat SecretServiceError = SecretServiceError{
ErrCode: EnumErrInvalidFileFormat,
ErrName: "SECRET_ERROR_INVALID_FILE_FORMAT",
ErrDesc: "the file/content format is invalid",
}
/*
AllSecretServiceErrs provides a slice of these for easier iteration when translating.
TECHNICALLY, because they are indexed in the order of their enums, you could
simplify and optimize translation by just doing e.g.

err = AllSecretServiceErrs[EnumErrProtocol]

But this should be considered UNSTABLE and UNSAFE due to it being potentially unpredictable in the future.
There are only 5 errors currently, so the performance benefits would be negligible compared to iteration.
If SecretService adds more errors, however, this may be more desirable.
*/
AllSecretServiceErrs []SecretServiceError = []SecretServiceError{
ErrSecretServiceProto,
ErrSecretServiceLocked,
ErrSecretServiceNoObj,
ErrSecretServiceExists,
ErrSecretServiceInvalidFormat,
}
)

View File

@ -1,42 +1,39 @@
package gosecret

import (
`github.com/godbus/dbus`
"github.com/godbus/dbus"
)

// NewService returns a pointer to a new Service.
// NewService returns a pointer to a new Service connection.
func NewService() (service *Service, err error) {

service = &Service{
Conn: nil,
Dbus: nil,
}
var svc Service = Service{}

if service.Conn, err = dbus.SessionBus(); err != nil {
if svc.Conn, err = dbus.SessionBus(); err != nil {
return
}
service.Dbus = service.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))
svc.Dbus = service.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))

if svc.Session, err = svc.Open(); err != nil {
return
}
}

// Path returns the path of the underlying Dbus connection.
func (s Service) Path() (path dbus.ObjectPath) {

// Remove this method in V1. It's bloat since we now have an exported Dbus.
path = s.Dbus.Path()
service = &svc

return
}

// Open returns a pointer to a Session from the Service.
func (s *Service) Open() (session *Session, err error) {
func (s *Service) Open() (session *Session, output dbus.Variant, err error) {

var output dbus.Variant
var path dbus.ObjectPath

// In *theory*, SecretService supports multiple "algorithms" for encryption in-transit, but I don't think it's implemented (yet)?
// TODO: confirm this.
// Possible flags are dbus.Flags consts: https://pkg.go.dev/github.com/godbus/dbus#Flags
// Oddly, there is no "None" flag. So it's explicitly specified as a null byte.
if err = s.Dbus.Call(
"org.freedesktop.Secret.Service.OpenSession", 0, "plain", dbus.MakeVariant(""),
DbusServiceOpenSession, 0x0, "plain", dbus.MakeVariant(""),
).Store(&output, &path); err != nil {
return
}
@ -46,7 +43,7 @@ func (s *Service) Open() (session *Session, err error) {
return
}

// Collections returns a slice of Collection keyrings accessible to this Service.
// Collections returns a slice of Collection items accessible to this Service.
func (s *Service) Collections() (collections []Collection, err error) {

var paths []dbus.ObjectPath
@ -67,7 +64,7 @@ func (s *Service) Collections() (collections []Collection, err error) {
return
}

// CreateCollection creates a new Collection (keyring) via a Service with the name specified by label.
// CreateCollection creates a new Collection (keyring) via a Service with the name specified by label and returns the new Collection.
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {

var variant *dbus.Variant

147
service_funcs.go.old Normal file
View File

@ -0,0 +1,147 @@
package gosecret

import (
`github.com/godbus/dbus`
)

// NewService returns a pointer to a new Service.
func NewService() (service *Service, err error) {

service = &Service{
Conn: nil,
Dbus: nil,
}

if service.Conn, err = dbus.SessionBus(); err != nil {
return
}
service.Dbus = service.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))

return
}

// Path returns the path of the underlying Dbus connection.
func (s *Service) Path() (path dbus.ObjectPath) {

// Remove this method in V1. It's bloat since we now have an exported Dbus.
path = s.Dbus.Path()

return
}

// Open returns a pointer to a Session from the Service.
func (s *Service) Open() (session *Session, err error) {

var output dbus.Variant
var path dbus.ObjectPath

if err = s.Dbus.Call(
"org.freedesktop.Secret.Service.OpenSession", 0, "plain", dbus.MakeVariant(""),
).Store(&output, &path); err != nil {
return
}

session = NewSession(s.Conn, path)

return
}

// Collections returns a slice of Collection keyrings accessible to this Service.
func (s *Service) Collections() (collections []Collection, err error) {

var paths []dbus.ObjectPath
var variant dbus.Variant

if variant, err = s.Dbus.GetProperty("org.freedesktop.Secret.Service.Collections"); err != nil {
return
}

paths = variant.Value().([]dbus.ObjectPath)

collections = make([]Collection, len(paths))

for idx, path := range paths {
collections[idx] = *NewCollection(s.Conn, path)
}

return
}

// CreateCollection creates a new Collection (keyring) via a Service with the name specified by label.
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {

var variant *dbus.Variant
var path dbus.ObjectPath
var promptPath dbus.ObjectPath
var prompt *Prompt
var props map[string]dbus.Variant = make(map[string]dbus.Variant)

props["org.freedesktop.Secret.Collection.Label"] = dbus.MakeVariant(label)

if err = s.Dbus.Call("org.freedesktop.Secret.Service.CreateCollection", 0, props, "").Store(&path, &promptPath); err != nil {
return
}

if isPrompt(promptPath) {

prompt = NewPrompt(s.Conn, promptPath)

if variant, err = prompt.Prompt(); err != nil {
return
}

path = variant.Value().(dbus.ObjectPath)
}

collection = NewCollection(s.Conn, path)

return
}

// Unlock unlocks a Locked Service.
func (s *Service) Unlock(object DBusObject) (err error) {

var unlocked []dbus.ObjectPath
var prompt *Prompt
var promptPath dbus.ObjectPath
var paths []dbus.ObjectPath = []dbus.ObjectPath{object.Path()}

if err = s.Dbus.Call("org.freedesktop.Secret.Service.Unlock", 0, paths).Store(&unlocked, &promptPath); err != nil {
return
}

if isPrompt(promptPath) {

prompt = NewPrompt(s.Conn, promptPath)

if _, err = prompt.Prompt(); err != nil {
return
}
}

return
}

// Lock locks an Unlocked Service.
func (s *Service) Lock(object DBusObject) (err error) {

var locked []dbus.ObjectPath
var prompt *Prompt
var promptPath dbus.ObjectPath
var paths []dbus.ObjectPath = []dbus.ObjectPath{object.Path()}

if err = s.Dbus.Call("org.freedesktop.Secret.Service.Lock", 0, paths).Store(&locked, &promptPath); err != nil {
return
}

if isPrompt(promptPath) {

prompt = NewPrompt(s.Conn, promptPath)

if _, err = prompt.Prompt(); err != nil {
return
}
}

return
}

28
sserror_funcs.go Normal file
View File

@ -0,0 +1,28 @@
package gosecret

/*
TranslateError translates a SecretServiceErrEnum into a SecretServiceError.
If a matching error was found, ok will be true and err will be the matching SecretServiceError.
If no matching error was found, however, then ok will be false and err will be ErrUnknownSecretServiceErr.
*/
func TranslateError(ssErr SecretServiceErrEnum) (ok bool, err error) {

err = ErrUnknownSecretServiceErr

for _, e := range AllSecretServiceErrs {
if e.ErrCode == ssErr {
ok = true
err = e
return
}
}

return
}

func (e SecretServiceError) Error() (errStr string) {

errStr = e.ErrDesc

return
}

120
types.go
View File

@ -1,9 +1,9 @@
package gosecret

import (
`time`
"time"

`github.com/godbus/dbus`
"github.com/godbus/dbus"
)

/*
@ -11,22 +11,70 @@ import (
*/
type MultiError struct {
// Errors is a slice of errors to combine/concatenate when .Error() is called.
Errors []error
Errors []error `json:"errors"`
// ErrorSep is a string to use to separate errors for .Error(). The default is "\n".
ErrorSep string
ErrorSep string `json:"separator"`
}

/*
SecretServiceError is a translated error from SecretService API.
See https://developer-old.gnome.org/libsecret/unstable/libsecret-SecretError.html#SecretError and
ErrSecretService* errors.
*/
type SecretServiceError struct {
// ErrCode is the SecretService API's enum value.
ErrCode SecretServiceErrEnum `json:"code"`
// ErrName is the SecretService API's error name.
ErrName string `json:"name"`
/*
ErrDesc is the actual error description/text.
This is what should be displayed to users, and is returned by SecretServiceError.Error.
*/
ErrDesc string `json:"desc"`
}

// ConnPathCheckResult contains the result of validConnPath.
type ConnPathCheckResult struct {
// ConnOK is true if the dbus.Conn is valid.
ConnOK bool
ConnOK bool `json:"conn"`
// PathOK is true if the Dbus path given is a valid type and value.
PathOK bool
PathOK bool `json:"path"`
}

// DBusObject is any type that has a Path method that returns a dbus.ObjectPath.
type DBusObject interface {
Path() dbus.ObjectPath
// DbusObject is a base struct type to be anonymized by other types.
type DbusObject struct {
// Conn is an active connection to the Dbus.
Conn *dbus.Conn `json:"-"`
// Dbus is the Dbus bus object.
Dbus dbus.BusObject `json:"-"`
}

/*
Prompt is an interface to handling unlocking prompts.
https://developer-old.gnome.org/libsecret/0.18/SecretPrompt.html
https://specifications.freedesktop.org/secret-service/latest/ch09.html
*/
type Prompt struct {
*DbusObject
}

/*
Service is a general SecretService interface, sort of handler for Dbus - it's used for fetching a Session, Collections, etc.
https://developer-old.gnome.org/libsecret/0.18/SecretService.html
https://specifications.freedesktop.org/secret-service/latest/re01.html
*/
type Service struct {
*DbusObject
Session *Session `json:"-"`
}

/*
Session is a session/instance/connection to SecretService.
https://developer-old.gnome.org/libsecret/0.18/SecretService.html
https://specifications.freedesktop.org/secret-service/latest/ch06.html
*/
type Session struct {
*DbusObject
}

/*
@ -36,10 +84,7 @@ type DBusObject interface {
https://specifications.freedesktop.org/secret-service/latest/ch03.html
*/
type Collection struct {
// Conn is an active connection to the Dbus.
Conn *dbus.Conn
// Dbus is the Dbus bus object.
Dbus dbus.BusObject
*DbusObject
// lastModified is unexported because it's important that API users don't change it; it's used by Collection.Modified.
lastModified time.Time
}
@ -50,22 +95,7 @@ type Collection struct {
https://specifications.freedesktop.org/secret-service/latest/re03.html
*/
type Item struct {
// Conn is an active connection to the Dbus.
Conn *dbus.Conn
// Dbus is the Dbus bus object.
Dbus dbus.BusObject
}

/*
Prompt is an interface to handling unlocking prompts.
https://developer-old.gnome.org/libsecret/0.18/SecretPrompt.html
https://specifications.freedesktop.org/secret-service/latest/ch09.html
*/
type Prompt struct {
// Conn is an active connection to the Dbus.
Conn *dbus.Conn
// Dbus is the Dbus bus object.
Dbus dbus.BusObject
*DbusObject
}

/*
@ -76,35 +106,11 @@ type Prompt struct {
*/
type Secret struct {
// Session is a Dbus object path for the associated Session.
Session dbus.ObjectPath
Session dbus.ObjectPath `json:"-"`
// Parameters are "algorithm dependent parameters for secret value encoding" - likely this will just be an empty byteslice.
Parameters []byte
Parameters []byte `json:"params"`
// Value is the secret's content in []byte format.
Value []byte
Value []byte `json:"value"`
// ContentType is the MIME type of Value.
ContentType string
}

/*
Service is a general SecretService interface, sort of handler for Dbus - it's used for fetching a Session, Collections, etc.
https://developer-old.gnome.org/libsecret/0.18/SecretService.html
https://specifications.freedesktop.org/secret-service/latest/re01.html
*/
type Service struct {
// Conn is an active connection to the Dbus.
Conn *dbus.Conn
// Dbus is the Dbus bus object.
Dbus dbus.BusObject
}

/*
Session is a session/instance/connection to SecretService.
https://developer-old.gnome.org/libsecret/0.18/SecretService.html
https://specifications.freedesktop.org/secret-service/latest/ch06.html
*/
type Session struct {
// Conn is an active connection to the Dbus.
Conn *dbus.Conn
// Dbus is the Dbus bus object.
Dbus dbus.BusObject
ContentType string `json:"content_type"`
}