From 3cab98e584deb0ecad66eac3a402d3fa17e01fd3 Mon Sep 17 00:00:00 2001 From: brent s Date: Sun, 28 Nov 2021 21:43:30 -0500 Subject: [PATCH] checking in some work- adding custom errors and low-level compat with SecretService, but it may not be needed. --- .idea/misc.xml | 4 -- .ref | 8 +++ consts.go | 106 ++++++++++++++++++++++++++++--- errs.go | 58 ++++++++++++++++- service_funcs.go | 37 +++++------ service_funcs.go.old | 147 +++++++++++++++++++++++++++++++++++++++++++ sserror_funcs.go | 28 +++++++++ types.go | 120 ++++++++++++++++++----------------- 8 files changed, 416 insertions(+), 92 deletions(-) delete mode 100644 .idea/misc.xml create mode 100644 service_funcs.go.old create mode 100644 sserror_funcs.go diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 063c298..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.ref b/.ref index ab4db83..c39a67a 100644 --- a/.ref +++ b/.ref @@ -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 diff --git a/consts.go b/consts.go index b350926..90c189c 100644 --- a/consts.go +++ b/consts.go @@ -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. @@ -63,7 +63,7 @@ const ( DbusInterfaceSession is the Dbus interface for working with a Session. Found at /org/freedesktop/secrets/session//(DbusInterfaceSession) */ - DbusInterfaceSession = DbusServiceBase + ".Session" + DbusInterfaceSession = DbusServiceBase + ".Session" // Methods @@ -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 +) diff --git a/errs.go b/errs.go index 6753ca9..e685895 100644 --- a/errs.go +++ b/errs.go @@ -1,15 +1,67 @@ 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") // ErrInvalidProperty indicates a dbus.Variant is not the "real" type expected. ErrInvalidProperty error = errors.New("invalid variant type; cannot convert") // ErrNoDbusConn gets triggered if a connection to Dbus can't be detected. - ErrNoDbusConn error = errors.New("no valid dbus connection") + 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, + } ) diff --git a/service_funcs.go b/service_funcs.go index 1c39cbc..af5e2c0 100644 --- a/service_funcs.go +++ b/service_funcs.go @@ -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)) - return -} + 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 diff --git a/service_funcs.go.old b/service_funcs.go.old new file mode 100644 index 0000000..c8b25b0 --- /dev/null +++ b/service_funcs.go.old @@ -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 +} diff --git a/sserror_funcs.go b/sserror_funcs.go new file mode 100644 index 0000000..ab0d80a --- /dev/null +++ b/sserror_funcs.go @@ -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 +} diff --git a/types.go b/types.go index d1dc6a7..c8a14a1 100644 --- a/types.go +++ b/types.go @@ -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"` }