service.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. package keychainlinker
  2. import (
  3. "fmt"
  4. "path"
  5. "strconv"
  6. "time"
  7. "github.com/godbus/dbus/v5"
  8. "github.com/godbus/dbus/v5/introspect"
  9. )
  10. const DEFAULT_COLLECTION = "/org/freedesktop/secrets/collections/default"
  11. type Cache struct {
  12. Collections map[dbus.ObjectPath]Collection
  13. }
  14. type Service struct {
  15. /*
  16. Working on implementing the org.freedesktop.Secret.Service interface, from their v0.2 spec:
  17. https://specifications.freedesktop.org/secret-service-spec/latest-single/#org.freedesktop.Secret.Service
  18. */
  19. Cache map[dbus.ObjectPath]*Collection
  20. Collections []dbus.ObjectPath
  21. PathCount int
  22. Alias map[string]dbus.ObjectPath
  23. SessionBase string // e.g. "/org/freedesktop/secrets/session/"
  24. CollectionBase string // e.g. "/org/freedesktop/secrets/collection/"
  25. }
  26. // implementing method to read the object property
  27. func (s *Service) Get(iface, property string) (dbus.Variant, *dbus.Error) {
  28. if iface != "/org/freedesktop/secrets/service" {
  29. return dbus.Variant{}, dbus.MakeFailedError(dbus.ErrMsgUnknownInterface)
  30. }
  31. switch property {
  32. case "Collections":
  33. return dbus.MakeVariant(s.Collections), nil
  34. default:
  35. return dbus.Variant{}, dbus.MakeFailedError(dbus.ErrMsgUnknownMethod)
  36. }
  37. }
  38. // implementing method to read the object property
  39. func (s *Service) Set(iface, property string, value dbus.Variant) *dbus.Error {
  40. if iface != "/org/freedesktop/secrets/service" {
  41. return dbus.MakeFailedError(dbus.ErrMsgUnknownInterface)
  42. }
  43. switch property {
  44. case "Collections":
  45. collections, ok := value.Value().([]dbus.ObjectPath)
  46. if !ok {
  47. return dbus.MakeFailedError(dbus.ErrMsgInvalidArg)
  48. }
  49. s.Collections = collections
  50. return nil
  51. default:
  52. return dbus.MakeFailedError(dbus.ErrMsgUnknownMethod)
  53. }
  54. }
  55. // implementing the get all method for the dbus interface
  56. func (s *Service) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
  57. if iface != "/org/freedesktop/secrets/service" {
  58. return nil, dbus.MakeFailedError(dbus.ErrMsgUnknownInterface)
  59. }
  60. return map[string]dbus.Variant{
  61. "Collections": dbus.MakeVariant(s.Collections),
  62. }, nil
  63. }
  64. /*
  65. implementing the properties interface
  66. func (s *Service) Get(iface, property string) (dbus.Variant, *dbus.Error) {
  67. if iface == "org.freedesktop.Secret.Service" && property == "Collections" {
  68. return dbus.MakeVariant(s.Collections), nil
  69. }
  70. return dbus.Variant{}, dbus.MakeFailedError(fmt.Errorf("no such property"))
  71. }
  72. func (s *Service) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
  73. if iface == "org.freedesktop.Secret.Service" {
  74. return map[string]dbus.Variant{
  75. "Collections": dbus.MakeVariant(s.Collections),
  76. }, nil
  77. }
  78. return nil, dbus.MakeFailedError(fmt.Errorf("no such interface"))
  79. }
  80. /*
  81. Create a new service interface
  82. */
  83. func NewService(base dbus.ObjectPath) *Service {
  84. return &Service{
  85. Cache: map[dbus.ObjectPath]*Collection{
  86. DEFAULT_COLLECTION: {
  87. Items: []dbus.ObjectPath{
  88. "/",
  89. },
  90. Label: "default",
  91. Cache: map[dbus.ObjectPath]CacheItem{},
  92. PathCount: 0,
  93. PathBase: fmt.Sprintf("%s/collection/default", base),
  94. Private: "true",
  95. Created: uint64(time.Now().Unix()),
  96. Modified: uint64(time.Now().Unix()),
  97. },
  98. },
  99. Collections: []dbus.ObjectPath{
  100. DEFAULT_COLLECTION,
  101. },
  102. PathCount: 0,
  103. Alias: map[string]dbus.ObjectPath{
  104. "default": DEFAULT_COLLECTION,
  105. },
  106. SessionBase: fmt.Sprintf("%s/session", base),
  107. CollectionBase: fmt.Sprintf("%s/collection", base),
  108. }
  109. }
  110. /*
  111. Opens a session for the Secret Service Interface
  112. :param algorithm: the encryption algorithm to use with the client
  113. :param input: the data used when implementing more advanced encryption algos
  114. */
  115. func (s *Service) OpenSession(algorithm string, input dbus.Variant) (dbus.Variant, dbus.ObjectPath, *dbus.Error) {
  116. if algorithm != "PLAIN" {
  117. return dbus.Variant{}, "/", dbus.MakeFailedError(fmt.Errorf("only PLAIN is supported"))
  118. }
  119. sessionPath := dbus.ObjectPath(path.Join(s.SessionBase, "1"))
  120. fmt.Println(sessionPath)
  121. return input, sessionPath, nil
  122. }
  123. /*
  124. Creates a collection with the Service object
  125. :param properties: a set of properties that are used by client apps
  126. :param alias: the shortname of the collection
  127. */
  128. func (s *Service) CreateCollection(properties map[string]dbus.Variant, alias string) (dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) {
  129. fmt.Printf("CreateCollection called with: %+v (alias: %s)\n", properties, alias)
  130. collPath := dbus.ObjectPath(path.Join(s.CollectionBase, strconv.Itoa(s.PathCount+1)))
  131. s.Collections = append(s.Collections, collPath)
  132. s.PathCount = s.PathCount + 1
  133. s.Alias[alias] = collPath
  134. return collPath, "/", nil
  135. }
  136. /*
  137. search for items in the keychain that satisfy 'attrs'
  138. :param attrs: a map of search criteria
  139. */
  140. func (s *Service) SearchItems(attrs map[string]string) ([]dbus.ObjectPath, []dbus.ObjectPath, *dbus.Error) {
  141. // Just return empty results for now
  142. found := []dbus.ObjectPath{}
  143. for _, v := range s.Cache {
  144. f, err := v.SearchItems(attrs)
  145. if err != nil {
  146. return []dbus.ObjectPath{}, []dbus.ObjectPath{}, err
  147. }
  148. found = append(found, f...)
  149. }
  150. return found, []dbus.ObjectPath{}, nil
  151. }
  152. /*
  153. attempts to return secrets that were either already unlocked, or unlocked without a prompt, in addition to
  154. a prompt path that can be used to unlock all remaining locked objects
  155. :param objects: a slice of dbus.Objects to unlock
  156. */
  157. func (s *Service) Unlock(objects []dbus.ObjectPath) ([]dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) {
  158. return []dbus.ObjectPath{}, dbus.ObjectPath("/"), nil // No prompt
  159. }
  160. /*
  161. Sets all dbus.Objects in 'objects' to the 'locked' position
  162. :param objects: a slice of dbus.Objects to unlock
  163. */
  164. func (s *Service) Lock(objects []dbus.ObjectPath) ([]dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) {
  165. return []dbus.ObjectPath{}, dbus.ObjectPath("/"), nil // No prompt
  166. }
  167. /*
  168. retrives secrets from an array of items/collections
  169. :param items: a slice of dbus.ObjectPath that will have their secrets returned
  170. */
  171. func (s *Service) GetSecrets(items []dbus.ObjectPath, session dbus.ObjectPath) (map[dbus.ObjectPath]SecretStruct, *dbus.Error) {
  172. return map[dbus.ObjectPath]SecretStruct{}, nil
  173. }
  174. /*
  175. Return a collection based on the alias name
  176. :param name: the alias to search for
  177. */
  178. func (s *Service) ReadAlias(name string) (dbus.ObjectPath, *dbus.Error) {
  179. objectPath, ok := s.Alias[name]
  180. if !ok {
  181. return dbus.ObjectPath("/"), &dbus.ErrMsgNoObject
  182. }
  183. return objectPath, nil
  184. }
  185. /*
  186. set the alias of the passed in collection
  187. :param name: the alias to set the collection to
  188. :param collection: the collection to modify
  189. */
  190. func (s *Service) SetAlias(name string, collection dbus.ObjectPath) *dbus.Error {
  191. s.Alias[name] = collection
  192. return nil
  193. }
  194. var ServiceNode = introspect.Node{
  195. Interfaces: []introspect.Interface{
  196. {
  197. Name: "org.freedesktop.Secret.Service",
  198. Methods: []introspect.Method{
  199. {
  200. Name: "OpenSession",
  201. Args: []introspect.Arg{
  202. {Name: "algorithm", Type: "s", Direction: "in"},
  203. {Name: "input", Type: "v", Direction: "in"},
  204. {Name: "output", Type: "(sv)", Direction: "out"},
  205. {Name: "session_path", Type: "o", Direction: "out"},
  206. },
  207. },
  208. {
  209. Name: "CreateCollection",
  210. Args: []introspect.Arg{
  211. {Name: "properties", Type: "a{sv}", Direction: "in"},
  212. {Name: "alias", Type: "s", Direction: "in"},
  213. {Name: "collection", Type: "o", Direction: "out"},
  214. {Name: "prompt", Type: "o", Direction: "out"},
  215. },
  216. },
  217. {
  218. Name: "SearchItems",
  219. Args: []introspect.Arg{
  220. {Name: "attributes", Type: "a{ss}", Direction: "in"},
  221. {Name: "locked", Type: "ao", Direction: "out"},
  222. {Name: "unlocked", Type: "ao", Direction: "out"},
  223. },
  224. },
  225. {
  226. Name: "Unlock",
  227. Args: []introspect.Arg{
  228. {Name: "objects", Type: "ao", Direction: "in"},
  229. {Name: "unlocked", Type: "ao", Direction: "out"},
  230. {Name: "prompt", Type: "o", Direction: "out"},
  231. },
  232. },
  233. {
  234. Name: "Lock",
  235. Args: []introspect.Arg{
  236. {Name: "objects", Type: "ao", Direction: "in"},
  237. {Name: "locked", Type: "ao", Direction: "out"},
  238. {Name: "prompt", Type: "o", Direction: "out"},
  239. },
  240. },
  241. {
  242. Name: "GetSecrets",
  243. Args: []introspect.Arg{
  244. {Name: "items", Type: "ao", Direction: "in"},
  245. {Name: "session", Type: "o", Direction: "in"},
  246. {Name: "secrets", Type: "a{oa(yays)}", Direction: "out"},
  247. },
  248. },
  249. {
  250. Name: "ReadAlias",
  251. Args: []introspect.Arg{
  252. {Name: "name", Type: "s", Direction: "in"},
  253. {Name: "collection", Type: "o", Direction: "out"},
  254. },
  255. },
  256. {
  257. Name: "SetAlias",
  258. Args: []introspect.Arg{
  259. {Name: "name", Type: "s", Direction: "in"},
  260. {Name: "collection", Type: "o", Direction: "in"},
  261. },
  262. },
  263. },
  264. Properties: []introspect.Property{
  265. {
  266. Name: "Collections",
  267. Type: "ao",
  268. Access: "read",
  269. },
  270. },
  271. },
  272. {
  273. Name: "org.freedesktop.DBus.Properties",
  274. Methods: []introspect.Method{
  275. {
  276. Name: "Get",
  277. Args: []introspect.Arg{
  278. {Name: "interface_name", Type: "s", Direction: "in"},
  279. {Name: "property_name", Type: "s", Direction: "in"},
  280. {Name: "value", Type: "v", Direction: "out"},
  281. },
  282. },
  283. {
  284. Name: "Set",
  285. Args: []introspect.Arg{
  286. {Name: "interface_name", Type: "s", Direction: "in"},
  287. {Name: "property_name", Type: "s", Direction: "in"},
  288. {Name: "value", Type: "v", Direction: "in"},
  289. },
  290. },
  291. {
  292. Name: "GetAll",
  293. Args: []introspect.Arg{
  294. {Name: "interface_name", Type: "s", Direction: "in"},
  295. {Name: "props", Type: "a{sv}", Direction: "out"},
  296. },
  297. },
  298. },
  299. },
  300. introspect.IntrospectData,
  301. },
  302. }