1
0

8 Ревизии 2946fa107c ... fe40f9713a

Автор SHA1 Съобщение Дата
  aeth fe40f9713a working on publishing to dbus properly преди 7 месеца
  aeth 057ce25175 working through publishing the interface properly преди 7 месеца
  aeth a29564f47e merging with upstream преди 7 месеца
  aeth a7b907b5a5 stuff преди 11 месеца
  aeth 2a4592a89b fixed checkboxes преди 11 месеца
  aeth ec742bf15b fixed checkboxes преди 11 месеца
  aeth cf6b4479e0 added quick link for secret api ref преди 11 месеца
  aeth 1329fb18f4 added quick link for secret api ref преди 11 месеца
променени са 7 файла, в които са добавени 469 реда и са изтрити 49 реда
  1. 10 1
      README.md
  2. 48 28
      main.go
  3. 142 12
      pkg/collection.go
  4. 196 7
      pkg/service.go
  5. 44 1
      pkg/xml.go
  6. 29 0
      scripts/test_add.py
  7. BIN
      test

+ 10 - 1
README.md

@@ -1,3 +1,12 @@
 # keychain-linker
 
-Linker system to bridge desktop clients attempting to query the org.freedesktop.Secret API to key management servers such as Vaultwarden, Hashicorp Vault, and the like
+Linker system to bridge desktop clients attempting to query the org.freedesktop.Secret API to key management servers such as Vaultwarden, Hashicorp Vault, and the like  
+API Reference: https://specifications.freedesktop.org/secret-service-spec/latest-single/#description  
+
+todos  
+- [x] implement org.freedesktop.Secret.Service 
+- [x] implement org.freedesktop.Secret.Collection   
+- [ ] implement org.freedesktop.Secret.Item  
+- [x] implement org.freedesktop.Secret.Session   
+- [x] implementorg.freedesktop.Secret.Prompt  
+- [ ] PoC write/read bridge for vaultwarden 

+ 48 - 28
main.go

@@ -9,6 +9,12 @@ import (
 	"github.com/godbus/dbus/v5/introspect"
 )
 
+const (
+	dbusName = "org.freedesktop.secrets"
+	objPath  = "/org/freedesktop/secrets/collection/default"
+	iface    = "org.freedesktop.Secret.Collection"
+)
+
 func main() {
 	conn, err := dbus.ConnectSessionBus()
 	if err != nil {
@@ -16,41 +22,55 @@ func main() {
 	}
 	defer conn.Close()
 
-	path := "/dev/aetherial/KeychainLinker"
-	session := &keychainlinker.Service{SessionBase: "/dev/aetherial/KeychainLinker/session/",
-		CollectionBase: "/dev/aetherial/KeychainLinker/collection/",
-		Collections:    []dbus.ObjectPath{}}
+	path := "/org/freedesktop/secrets"
+	service := keychainlinker.NewService(dbus.ObjectPath(path))
+
+	conn.Export(service, dbus.ObjectPath(path), "org.freedesktop.Secret.Service")
 
-	conn.Export(session, dbus.ObjectPath(path), "dev.aetherial.git.KeychainLinker.Service")
 	conn.Export(introspect.Introspectable(keychainlinker.DbusAdv), dbus.ObjectPath(path),
 		"org.freedesktop.DBus.Introspectable")
-	node := introspect.Node{
-		Interfaces: []introspect.Interface{
-			{
-				Name: "org.freedesktop.secret.Service",
-				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"}}},
+
+	/*
+		node := introspect.Node{
+			Interfaces: []introspect.Interface{
+				{
+					Name: "org.freedesktop.Secret.Service",
+					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"}}},
+					},
 				},
 			},
-		},
-	}
-	conn.Export(introspect.Introspectable(introspect.NewIntrospectable(&node)), "/org/freedesktop/secret", "org.freedesktop.DBus.Introspectable")
-	node = introspect.Node{
-		Interfaces: []introspect.Interface{
-			{
-				Name: "org.freedesktop.secret.Collection",
-				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"}}},
+		}
+	*/
+	conn.Export(introspect.NewIntrospectable(&keychainlinker.ServiceNode), dbus.ObjectPath(path), "org.freedesktop.DBus.Introspectable")
+	collection := &keychainlinker.Collection{}
+	conn.Export(collection, objPath, iface)
+	/*
+		collectionNode := introspect.Node{
+			Interfaces: []introspect.Interface{
+				{
+					Name: iface,
+					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: "SearchItems",
+							Args: []introspect.Arg{
+								{Name: "attributes", Type: "a{ss}", Direction: "in"},
+								{Name: "locked", Type: "ao", Direction: "out"},
+								{Name: "unlocked", Type: "ao", Direction: "out"},
+							},
+						},
+					},
 				},
 			},
-		},
-	}
-	conn.Export(introspect.Introspectable(introspect.NewIntrospectable(&node)), "/org/freedesktop/secret", "org.freedesktop.DBus.IntrospectData")
+		}
+	*/
+	conn.Export(introspect.NewIntrospectable(&keychainlinker.CollectionNode), dbus.ObjectPath(path+"/collection/default"), "org.freedesktop.DBus.Introspectable")
 
-	reply, err := conn.RequestName("dev.aetherial.git.KeychainLinker.Service",
+	reply, err := conn.RequestName(dbusName,
 		dbus.NameFlagDoNotQueue)
 	if err != nil {
 		panic(err)
@@ -59,6 +79,6 @@ func main() {
 		fmt.Fprintln(os.Stderr, "name already taken")
 		os.Exit(1)
 	}
-	fmt.Println("Listening on dev.aetherial.git.KeychainLinker.Service / /dev/aetherial/git/KeychainLinker/Service ...")
+	fmt.Println("Listening on org.freedesktop.secrets / /org/freedesktop/secrets ...")
 	select {}
 }

+ 142 - 12
pkg/collection.go

@@ -1,18 +1,54 @@
 package keychainlinker
 
-import "github.com/godbus/dbus/v5"
+import (
+	"fmt"
+	"strconv"
+
+	"github.com/godbus/dbus/v5"
+	"github.com/godbus/dbus/v5/introspect"
+)
+
+type CacheItem struct {
+	Secret      SecretStruct
+	Label       string
+	LookupProps map[string]string
+}
 
 type Collection struct {
 	/*
 		Implying the org.freedesktop.Secret.Collection interface as per the v0.2 spec:
 		https://specifications.freedesktop.org/secret-service-spec/latest-single/#org.freedesktop.Secret.Collection
 	*/
-	Items    []dbus.ObjectPath // items in the collection
-	Private  string            // specifies whether the collection is private or not
-	Label    string            //  The displayable label of this collection.
-	Locked   string            //  Whether the collection is locked and must be authenticated by the client application.
-	Created  uint64            //  The unix time when the collection was created.
-	Modified uint64            //  The unix time when the collection was last modified.
+	Items     []dbus.ObjectPath             // items in the collection
+	Cache     map[dbus.ObjectPath]CacheItem // memory cache for the collection. Will be removed later
+	PathCount int
+	PathBase  string
+	Private   string // specifies whether the collection is private or not
+	Label     string //  The displayable label of this collection.
+	Locked    string //  Whether the collection is locked and must be authenticated by the client application.
+	Created   uint64 //  The unix time when the collection was created.
+	Modified  uint64 //  The unix time when the collection was last modified.
+}
+
+func (c *Collection) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
+	if iface == "org.freedesktop.Secret.Collection" {
+		return map[string]dbus.Variant{
+			"Label":    dbus.MakeVariant(c.Label),
+			"Locked":   dbus.MakeVariant(c.Locked),
+			"Created":  dbus.MakeVariant(c.Created),
+			"Modified": dbus.MakeVariant(c.Modified),
+			"Items":    dbus.MakeVariant(c.Items),
+		}, nil
+	}
+	return nil, dbus.MakeFailedError(fmt.Errorf("no such interface"))
+}
+
+/*
+Create a path to assign the secret
+*/
+func (c *Collection) pathBuilder() dbus.ObjectPath {
+	return dbus.ObjectPath(c.PathBase + "/" + strconv.Itoa(c.PathCount+1))
+
 }
 
 // deletes the collection, returning an object path tied to a prompt incase it is necessary.
@@ -26,20 +62,61 @@ Searches the collection for matching items
 	:param attr: the attributes to attempt to match to a key in the collection
 */
 func (c *Collection) SearchItems(attr map[string]string) ([]dbus.ObjectPath, *dbus.Error) {
-	// implement a recursive searching thing
-	return []dbus.ObjectPath{}, nil
+	fmt.Println("recv call for SearchItems")
+	matched := []dbus.ObjectPath{}
+	for path, sec := range c.Cache {
+		secretAttr := sec.LookupProps
+		var passedLookup bool
+		passedLookup = false
+		for k, v := range attr {
+			got, ok := secretAttr[k]
+			if !ok {
+				continue
+			}
+			if got == v {
+				passedLookup = true
+				continue
+			}
+		}
+		if passedLookup {
+			matched = append(matched, path)
+		}
+
+	}
+
+	return matched, nil
 }
 
 /*
 Creates a new item in the collection with the properties defined in 'props'.
 Returns the items dbus object path, as well as a path to a dbus prompt incase it is required to edit
 
-	:param fields: a map of properties to assign to the item. Will be used to match during lookups
+	:param props: a map of properties to assign to the item. Will be used to match during lookups
 	:param secret: the secret to encode into the collection
 	:param replace: replace secret if a matching one is found in the store
 */
-func (c *Collection) CreateItem(fields map[string]dbus.Variant, secret SecretStruct, replace bool) (dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) {
-	return dbus.ObjectPath("/"), dbus.ObjectPath("/"), nil
+func (c *Collection) CreateItem(props map[string]dbus.Variant, secret SecretStruct, replace bool) (dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) {
+	v := props["org.freedesktop.Secret.Item.Attributes"]
+	attrs, ok := v.Value().(map[string]string)
+	if !ok {
+		return dbus.ObjectPath("/"), dbus.ObjectPath("/"), &dbus.ErrMsgNoObject
+	}
+	label, ok := props["org.freedesktop.Secret.Item.Label"].Value().(string)
+	if !ok {
+		// no label found
+		label = ""
+	}
+	if !replace {
+		// implement the the replace option
+	}
+	path := c.pathBuilder()
+	c.Cache[path] = CacheItem{
+		LookupProps: attrs,
+		Label:       label,
+		Secret:      secret,
+	}
+
+	return path, dbus.ObjectPath("/"), nil
 }
 
 // implementing method to read the object property
@@ -81,3 +158,56 @@ func (c *Collection) Set(iface, property string, value dbus.Variant) *dbus.Error
 	}
 
 }
+
+var CollectionNode = introspect.Node{
+	Name: "/org/freedesktop/secrets/collection/default", // or your collection path
+	Interfaces: []introspect.Interface{
+		{
+			Name: "org.freedesktop.Secret.Collection",
+			Methods: []introspect.Method{
+				{
+					Name: "CreateItem",
+					Args: []introspect.Arg{
+						{Name: "properties", Type: "a{sv}", Direction: "in"},
+						{Name: "secret", Type: "v", Direction: "in"},
+						{Name: "replace", Type: "b", Direction: "in"},
+						{Name: "item", Type: "o", Direction: "out"},
+						{Name: "prompt", Type: "o", Direction: "out"},
+					},
+				},
+				{
+					Name: "SearchItems",
+					Args: []introspect.Arg{
+						{Name: "attributes", Type: "a{ss}", Direction: "in"},
+						{Name: "unlocked", Type: "ao", Direction: "out"},
+						{Name: "locked", Type: "ao", Direction: "out"},
+					},
+				},
+				{
+					Name: "Delete",
+					Args: []introspect.Arg{
+						{Name: "prompt", Type: "o", Direction: "out"},
+					},
+				},
+			},
+			Properties: []introspect.Property{
+				{Name: "Items", Type: "ao", Access: "read"},
+				{Name: "Label", Type: "s", Access: "readwrite"},
+				{Name: "Locked", Type: "b", Access: "read"},
+				{Name: "Created", Type: "t", Access: "read"},
+				{Name: "Modified", Type: "t", Access: "read"},
+			},
+		},
+		{
+			Name: "org.freedesktop.DBus.Introspectable",
+			Methods: []introspect.Method{
+				{
+					Name: "Introspect",
+					Args: []introspect.Arg{
+						{Name: "data", Type: "s", Direction: "out"},
+					},
+				},
+			},
+		},
+	},
+}

+ 196 - 7
pkg/service.go

@@ -3,23 +3,35 @@ 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/secret/service" {
+	if iface != "/org/freedesktop/secrets/service" {
 		return dbus.Variant{}, dbus.MakeFailedError(dbus.ErrMsgUnknownInterface)
 	}
 	switch property {
@@ -32,7 +44,7 @@ func (s *Service) Get(iface, property string) (dbus.Variant, *dbus.Error) {
 
 // implementing method to read the object property
 func (s *Service) Set(iface, property string, value dbus.Variant) *dbus.Error {
-	if iface != "org/freedesktop/secret/service" {
+	if iface != "/org/freedesktop/secrets/service" {
 		return dbus.MakeFailedError(dbus.ErrMsgUnknownInterface)
 	}
 	switch property {
@@ -51,7 +63,7 @@ func (s *Service) Set(iface, property string, value dbus.Variant) *dbus.Error {
 
 // 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.secret.Service" {
+	if iface != "/org/freedesktop/secrets/service" {
 		return nil, dbus.MakeFailedError(dbus.ErrMsgUnknownInterface)
 	}
 
@@ -60,6 +72,56 @@ func (s *Service) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
 	}, 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
 
@@ -70,8 +132,8 @@ func (s *Service) OpenSession(algorithm string, input dbus.Variant) (dbus.Varian
 	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
 }
 
@@ -82,8 +144,11 @@ Creates a collection with the Service object
 	:param alias: the shortname of the collection
 */
 func (s *Service) CreateCollection(properties map[string]dbus.Variant, alias string) (dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) {
-	collPath := dbus.ObjectPath(path.Join(s.CollectionBase, "login"))
+	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
 }
 
@@ -94,7 +159,16 @@ search for items in the keychain that satisfy 'attrs'
 */
 func (s *Service) SearchItems(attrs map[string]string) ([]dbus.ObjectPath, []dbus.ObjectPath, *dbus.Error) {
 	// Just return empty results for now
-	return []dbus.ObjectPath{}, []dbus.ObjectPath{}, nil
+	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
 }
 
 /*
@@ -131,7 +205,11 @@ 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) {
-	return dbus.ObjectPath(""), nil
+	objectPath, ok := s.Alias[name]
+	if !ok {
+		return dbus.ObjectPath("/"), &dbus.ErrMsgNoObject
+	}
+	return objectPath, nil
 
 }
 
@@ -142,6 +220,117 @@ set the alias of the passed in collection
 	: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,
+	},
+}

+ 44 - 1
pkg/xml.go

@@ -4,9 +4,52 @@ import (
 	"github.com/godbus/dbus/v5/introspect"
 )
 
+const CollectionAdv = `
+
+	  <interface name="org.freedesktop.Secret.Collection">
+
+		<property name="Label" type="s" access="readwrite"/>
+		<property name="Locked" type="b" access="read"/>
+		<property name="Created" type="u" access="read"/>
+		<property name="Modified" type="u" access="read"/>
+		<property name="Items" type="ao" access="read"/>
+
+		<method name="Delete">
+		  <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/>
+		</method>
+
+		<method name="SearchItems">
+		  <arg name="attributes" type="a{ss}" direction="in"/>
+		  <arg name="unlocked" type="ao" direction="out"/>
+		  <arg name="locked" type="ao" direction="out"/>
+		</method>
+
+		<method name="CreateItem">
+		  <arg name="properties" type="a{sv}" direction="in"/>
+		  <arg name="secret" type="(oayays)" direction="in"/>
+		  <arg name="replace" type="b" direction="in"/>
+		  <arg name="item" type="o" direction="out"/>
+		  <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/>
+		</method>
+
+		<signal name="ItemCreated">
+		  <arg name="item" type="o"/>
+		</signal>
+
+		<signal name="ItemDeleted">
+		  <arg name="item" type="o"/>
+		</signal>
+
+		<signal name="ItemChanged">
+		  <arg name="item" type="o"/>
+		</signal>
+	</interface>
+`
+
 const DbusAdv = `
 <node>
-	<interface name="dev.aetherial.git.KeychainLinker.Service">
+
+	<interface name="org.freedesktop.Secret.Service">
 		<method name="OpenSession">
 		  <arg name="algorithm" type="s" direction="in"/>
 		  <arg name="input" type="v" direction="in"/>

+ 29 - 0
scripts/test_add.py

@@ -0,0 +1,29 @@
+import secretstorage
+import dbus
+
+# Connect to the session bus
+bus = dbus.SessionBus()
+
+# Connect to the Secret Service
+connection = secretstorage.dbus_init()
+collection = secretstorage.get_default_collection(connection)
+
+
+# Define attributes and label
+attributes = {
+    'username': 'alice',
+    'service': 'example-app'
+}
+label = 'My Example Secret'
+secret = 'super_secret_password_123'
+
+# Create the item
+item = collection.create_item(
+    label=label,
+    attributes=attributes,
+    secret=secret,
+    replace=True  # Replace if an item with same attributes exists
+)
+
+print(f"Secret stored with label: {label}")
+