// +build !gnome_keyring package keyring import ( "fmt" dbus "github.com/guelfey/go.dbus" ) const ( ssServiceName = "org.freedesktop.secrets" ssServicePath = "/org/freedesktop/secrets" ssCollectionPath = "/org/freedesktop/secrets/aliases/default" ssServiceIface = "org.freedesktop.Secret.Service." ssSessionIface = "org.freedesktop.Secret.Session." ssCollectionIface = "org.freedesktop.Secret.Collection." ssItemIface = "org.freedesktop.Secret.Item." ssPromptIface = "org.freedesktop.Secret.Prompt." ) // ssSecret corresponds to org.freedesktop.Secret.Item // Note: Order is important type ssSecret struct { Session dbus.ObjectPath Parameters []byte Value []byte ContentType string `dbus:"content_type"` } // newSSSecret prepares an ssSecret for use // Uses text/plain as the Content-type which may need to change in the future func newSSSecret(session dbus.ObjectPath, secret string) (s ssSecret) { s = ssSecret{ ContentType: "text/plain; charset=utf8", Parameters: []byte{}, Session: session, Value: []byte(secret), } return } // ssProvider implements the provider interface freedesktop SecretService type ssProvider struct { *dbus.Conn srv *dbus.Object } // This is used to open a seassion for every get/set. Alternative might be to // defer() the call to close when constructing the ssProvider func (s *ssProvider) openSession() (*dbus.Object, error) { var disregard dbus.Variant var sessionPath dbus.ObjectPath method := fmt.Sprint(ssServiceIface, "OpenSession") err := s.srv.Call(method, 0, "plain", dbus.MakeVariant("")).Store(&disregard, &sessionPath) if err != nil { return nil, err } return s.Object(ssServiceName, sessionPath), nil } // Unsure how the .Prompt call surfaces, it hasn't come up. func (s *ssProvider) unlock(p dbus.ObjectPath) error { var unlocked []dbus.ObjectPath var prompt dbus.ObjectPath method := fmt.Sprint(ssServiceIface, "Unlock") err := s.srv.Call(method, 0, []dbus.ObjectPath{p}).Store(&unlocked, &prompt) if err != nil { return fmt.Errorf("keyring/dbus: Unlock error: %s", err) } if prompt != dbus.ObjectPath("/") { method = fmt.Sprint(ssPromptIface, "Prompt") call := s.Object(ssServiceName, prompt).Call(method, 0, "unlock") return call.Err } return nil } func (s *ssProvider) Get(c, u string) (string, error) { results := []dbus.ObjectPath{} var secret ssSecret search := map[string]string{ "username": u, "service": c, } session, err := s.openSession() if err != nil { return "", err } defer session.Call(fmt.Sprint(ssSessionIface, "Close"), 0) s.unlock(ssCollectionPath) collection := s.Object(ssServiceName, ssCollectionPath) method := fmt.Sprint(ssCollectionIface, "SearchItems") call := collection.Call(method, 0, search) err = call.Store(&results) if call.Err != nil { return "", call.Err } // results is a slice. Just grab the first one. if len(results) == 0 { return "", ErrNotFound } method = fmt.Sprint(ssItemIface, "GetSecret") err = s.Object(ssServiceName, results[0]).Call(method, 0, session.Path()).Store(&secret) if err != nil { return "", err } return string(secret.Value), nil } func (s *ssProvider) Set(c, u, p string) error { var item, prompt dbus.ObjectPath properties := map[string]dbus.Variant{ "org.freedesktop.Secret.Item.Label": dbus.MakeVariant(fmt.Sprintf("%s - %s", u, c)), "org.freedesktop.Secret.Item.Attributes": dbus.MakeVariant(map[string]string{ "username": u, "service": c, }), } session, err := s.openSession() if err != nil { return err } defer session.Call(fmt.Sprint(ssSessionIface, "Close"), 0) s.unlock(ssCollectionPath) collection := s.Object(ssServiceName, ssCollectionPath) secret := newSSSecret(session.Path(), p) // the bool is "replace" err = collection.Call(fmt.Sprint(ssCollectionIface, "CreateItem"), 0, properties, secret, true).Store(&item, &prompt) if err != nil { return fmt.Errorf("keyring/dbus: CreateItem error: %s", err) } if prompt != "/" { s.Object(ssServiceName, prompt).Call(fmt.Sprint(ssPromptIface, "Prompt"), 0, "unlock") } return nil } func initializeProvider() (provider, error) { conn, err := dbus.SessionBus() if err != nil { return nil, err } srv := conn.Object(ssServiceName, ssServicePath) p := &ssProvider{conn, srv} return p, nil }