package keychainlinker import ( "fmt" "path" "strconv" "time" "github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5/introspect" ) const DEFAULT_COLLECTION = "/org/freedesktop/secrets/collections/default" type Cache struct { Collections map[dbus.ObjectPath]Collection } type Service struct { /* Working on implementing the org.freedesktop.Secret.Service interface, from their v0.2 spec: https://specifications.freedesktop.org/secret-service-spec/latest-single/#org.freedesktop.Secret.Service */ Cache map[dbus.ObjectPath]*Collection Collections []dbus.ObjectPath PathCount int Alias map[string]dbus.ObjectPath SessionBase string // e.g. "/org/freedesktop/secrets/session/" CollectionBase string // e.g. "/org/freedesktop/secrets/collection/" } // implementing method to read the object property func (s *Service) Get(iface, property string) (dbus.Variant, *dbus.Error) { if iface != "/org/freedesktop/secrets/service" { return dbus.Variant{}, dbus.MakeFailedError(dbus.ErrMsgUnknownInterface) } switch property { case "Collections": return dbus.MakeVariant(s.Collections), nil default: return dbus.Variant{}, dbus.MakeFailedError(dbus.ErrMsgUnknownMethod) } } // implementing method to read the object property func (s *Service) Set(iface, property string, value dbus.Variant) *dbus.Error { if iface != "/org/freedesktop/secrets/service" { return dbus.MakeFailedError(dbus.ErrMsgUnknownInterface) } switch property { case "Collections": collections, ok := value.Value().([]dbus.ObjectPath) if !ok { return dbus.MakeFailedError(dbus.ErrMsgInvalidArg) } s.Collections = collections return nil default: return dbus.MakeFailedError(dbus.ErrMsgUnknownMethod) } } // implementing the get all method for the dbus interface func (s *Service) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) { if iface != "/org/freedesktop/secrets/service" { return nil, dbus.MakeFailedError(dbus.ErrMsgUnknownInterface) } return map[string]dbus.Variant{ "Collections": dbus.MakeVariant(s.Collections), }, nil } /* implementing the properties interface func (s *Service) Get(iface, property string) (dbus.Variant, *dbus.Error) { if iface == "org.freedesktop.Secret.Service" && property == "Collections" { return dbus.MakeVariant(s.Collections), nil } return dbus.Variant{}, dbus.MakeFailedError(fmt.Errorf("no such property")) } func (s *Service) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) { if iface == "org.freedesktop.Secret.Service" { return map[string]dbus.Variant{ "Collections": dbus.MakeVariant(s.Collections), }, nil } return nil, dbus.MakeFailedError(fmt.Errorf("no such interface")) } /* Create a new service interface */ func NewService(base dbus.ObjectPath) *Service { return &Service{ Cache: map[dbus.ObjectPath]*Collection{ DEFAULT_COLLECTION: { Items: []dbus.ObjectPath{ "/", }, Label: "default", Cache: map[dbus.ObjectPath]CacheItem{}, PathCount: 0, PathBase: fmt.Sprintf("%s/collection/default", base), Private: "true", Created: uint64(time.Now().Unix()), Modified: uint64(time.Now().Unix()), }, }, Collections: []dbus.ObjectPath{ DEFAULT_COLLECTION, }, PathCount: 0, Alias: map[string]dbus.ObjectPath{ "default": DEFAULT_COLLECTION, }, SessionBase: fmt.Sprintf("%s/session", base), CollectionBase: fmt.Sprintf("%s/collection", base), } } /* Opens a session for the Secret Service Interface :param algorithm: the encryption algorithm to use with the client :param input: the data used when implementing more advanced encryption algos */ func (s *Service) OpenSession(algorithm string, input dbus.Variant) (dbus.Variant, dbus.ObjectPath, *dbus.Error) { if algorithm != "PLAIN" { return dbus.Variant{}, "/", dbus.MakeFailedError(fmt.Errorf("only PLAIN is supported")) } sessionPath := dbus.ObjectPath(path.Join(s.SessionBase, "1")) fmt.Println(sessionPath) return input, sessionPath, nil } /* Creates a collection with the Service object :param properties: a set of properties that are used by client apps :param alias: the shortname of the collection */ func (s *Service) CreateCollection(properties map[string]dbus.Variant, alias string) (dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) { fmt.Printf("CreateCollection called with: %+v (alias: %s)\n", properties, alias) collPath := dbus.ObjectPath(path.Join(s.CollectionBase, strconv.Itoa(s.PathCount+1))) s.Collections = append(s.Collections, collPath) s.PathCount = s.PathCount + 1 s.Alias[alias] = collPath return collPath, "/", nil } /* search for items in the keychain that satisfy 'attrs' :param attrs: a map of search criteria */ func (s *Service) SearchItems(attrs map[string]string) ([]dbus.ObjectPath, []dbus.ObjectPath, *dbus.Error) { // Just return empty results for now found := []dbus.ObjectPath{} for _, v := range s.Cache { f, err := v.SearchItems(attrs) if err != nil { return []dbus.ObjectPath{}, []dbus.ObjectPath{}, err } found = append(found, f...) } return found, []dbus.ObjectPath{}, nil } /* attempts to return secrets that were either already unlocked, or unlocked without a prompt, in addition to a prompt path that can be used to unlock all remaining locked objects :param objects: a slice of dbus.Objects to unlock */ func (s *Service) Unlock(objects []dbus.ObjectPath) ([]dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) { return []dbus.ObjectPath{}, dbus.ObjectPath("/"), nil // No prompt } /* Sets all dbus.Objects in 'objects' to the 'locked' position :param objects: a slice of dbus.Objects to unlock */ func (s *Service) Lock(objects []dbus.ObjectPath) ([]dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) { return []dbus.ObjectPath{}, dbus.ObjectPath("/"), nil // No prompt } /* retrives secrets from an array of items/collections :param items: a slice of dbus.ObjectPath that will have their secrets returned */ func (s *Service) GetSecrets(items []dbus.ObjectPath, session dbus.ObjectPath) (map[dbus.ObjectPath]SecretStruct, *dbus.Error) { return map[dbus.ObjectPath]SecretStruct{}, nil } /* Return a collection based on the alias name :param name: the alias to search for */ func (s *Service) ReadAlias(name string) (dbus.ObjectPath, *dbus.Error) { objectPath, ok := s.Alias[name] if !ok { return dbus.ObjectPath("/"), &dbus.ErrMsgNoObject } return objectPath, nil } /* set the alias of the passed in collection :param name: the alias to set the collection to :param collection: the collection to modify */ func (s *Service) SetAlias(name string, collection dbus.ObjectPath) *dbus.Error { s.Alias[name] = collection return nil } var ServiceNode = introspect.Node{ Interfaces: []introspect.Interface{ { Name: "org.freedesktop.Secret.Service", Methods: []introspect.Method{ { Name: "OpenSession", Args: []introspect.Arg{ {Name: "algorithm", Type: "s", Direction: "in"}, {Name: "input", Type: "v", Direction: "in"}, {Name: "output", Type: "(sv)", Direction: "out"}, {Name: "session_path", Type: "o", Direction: "out"}, }, }, { Name: "CreateCollection", Args: []introspect.Arg{ {Name: "properties", Type: "a{sv}", Direction: "in"}, {Name: "alias", Type: "s", Direction: "in"}, {Name: "collection", Type: "o", Direction: "out"}, {Name: "prompt", Type: "o", Direction: "out"}, }, }, { Name: "SearchItems", Args: []introspect.Arg{ {Name: "attributes", Type: "a{ss}", Direction: "in"}, {Name: "locked", Type: "ao", Direction: "out"}, {Name: "unlocked", Type: "ao", Direction: "out"}, }, }, { Name: "Unlock", Args: []introspect.Arg{ {Name: "objects", Type: "ao", Direction: "in"}, {Name: "unlocked", Type: "ao", Direction: "out"}, {Name: "prompt", Type: "o", Direction: "out"}, }, }, { Name: "Lock", Args: []introspect.Arg{ {Name: "objects", Type: "ao", Direction: "in"}, {Name: "locked", Type: "ao", Direction: "out"}, {Name: "prompt", Type: "o", Direction: "out"}, }, }, { Name: "GetSecrets", Args: []introspect.Arg{ {Name: "items", Type: "ao", Direction: "in"}, {Name: "session", Type: "o", Direction: "in"}, {Name: "secrets", Type: "a{oa(yays)}", Direction: "out"}, }, }, { Name: "ReadAlias", Args: []introspect.Arg{ {Name: "name", Type: "s", Direction: "in"}, {Name: "collection", Type: "o", Direction: "out"}, }, }, { Name: "SetAlias", Args: []introspect.Arg{ {Name: "name", Type: "s", Direction: "in"}, {Name: "collection", Type: "o", Direction: "in"}, }, }, }, Properties: []introspect.Property{ { Name: "Collections", Type: "ao", Access: "read", }, }, }, { Name: "org.freedesktop.DBus.Properties", Methods: []introspect.Method{ { Name: "Get", Args: []introspect.Arg{ {Name: "interface_name", Type: "s", Direction: "in"}, {Name: "property_name", Type: "s", Direction: "in"}, {Name: "value", Type: "v", Direction: "out"}, }, }, { Name: "Set", Args: []introspect.Arg{ {Name: "interface_name", Type: "s", Direction: "in"}, {Name: "property_name", Type: "s", Direction: "in"}, {Name: "value", Type: "v", Direction: "in"}, }, }, { Name: "GetAll", Args: []introspect.Arg{ {Name: "interface_name", Type: "s", Direction: "in"}, {Name: "props", Type: "a{sv}", Direction: "out"}, }, }, }, }, introspect.IntrospectData, }, }