summaryrefslogtreecommitdiff
path: root/vendor/github.com/99designs/keyring
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
committermo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
commit20ef0d92694465ac86b550df139e8366a0a2b4fa (patch)
tree3f14589e1ce6eb9306a3af31c3a1f9e1af5ed637 /vendor/github.com/99designs/keyring
parent44e0d272c040cdc53a98b9f1dc58ae7da67752e6 (diff)
feat: connect to spicedb
Diffstat (limited to 'vendor/github.com/99designs/keyring')
-rw-r--r--vendor/github.com/99designs/keyring/.gitattributes1
-rw-r--r--vendor/github.com/99designs/keyring/.gitignore1
-rw-r--r--vendor/github.com/99designs/keyring/.golangci.yml29
-rw-r--r--vendor/github.com/99designs/keyring/LICENSE22
-rw-r--r--vendor/github.com/99designs/keyring/README.md67
-rw-r--r--vendor/github.com/99designs/keyring/Vagrantfile85
-rw-r--r--vendor/github.com/99designs/keyring/array.go54
-rw-r--r--vendor/github.com/99designs/keyring/config.go58
-rw-r--r--vendor/github.com/99designs/keyring/docker-compose.yml7
-rw-r--r--vendor/github.com/99designs/keyring/file.go180
-rw-r--r--vendor/github.com/99designs/keyring/keychain.go301
-rw-r--r--vendor/github.com/99designs/keyring/keyctl.go327
-rw-r--r--vendor/github.com/99designs/keyring/keyring.go134
-rw-r--r--vendor/github.com/99designs/keyring/kwallet.go237
-rw-r--r--vendor/github.com/99designs/keyring/pass.go166
-rw-r--r--vendor/github.com/99designs/keyring/prompt.go27
-rw-r--r--vendor/github.com/99designs/keyring/secretservice.go293
-rw-r--r--vendor/github.com/99designs/keyring/tilde.go22
-rw-r--r--vendor/github.com/99designs/keyring/wincred.go98
19 files changed, 2109 insertions, 0 deletions
diff --git a/vendor/github.com/99designs/keyring/.gitattributes b/vendor/github.com/99designs/keyring/.gitattributes
new file mode 100644
index 0000000..d207b18
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/.gitattributes
@@ -0,0 +1 @@
+*.go text eol=lf
diff --git a/vendor/github.com/99designs/keyring/.gitignore b/vendor/github.com/99designs/keyring/.gitignore
new file mode 100644
index 0000000..8000dd9
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/.gitignore
@@ -0,0 +1 @@
+.vagrant
diff --git a/vendor/github.com/99designs/keyring/.golangci.yml b/vendor/github.com/99designs/keyring/.golangci.yml
new file mode 100644
index 0000000..f83428e
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/.golangci.yml
@@ -0,0 +1,29 @@
+linters:
+ enable:
+ - bodyclose
+ - contextcheck
+ - depguard
+ - durationcheck
+ - dupl
+ - errchkjson
+ - errname
+ - exhaustive
+ - exportloopref
+ - gocritic
+ - gofmt
+ - goimports
+ - makezero
+ - misspell
+ - nakedret
+ - nilerr
+ - nilnil
+ - noctx
+ - prealloc
+ - revive
+ # - rowserrcheck
+ - thelper
+ - tparallel
+ - unconvert
+ - unparam
+ # - wastedassign
+ - whitespace
diff --git a/vendor/github.com/99designs/keyring/LICENSE b/vendor/github.com/99designs/keyring/LICENSE
new file mode 100644
index 0000000..0fe9e46
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 99designs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/github.com/99designs/keyring/README.md b/vendor/github.com/99designs/keyring/README.md
new file mode 100644
index 0000000..629a6aa
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/README.md
@@ -0,0 +1,67 @@
+Keyring
+=======
+[![Build Status](https://github.com/99designs/keyring/workflows/Continuous%20Integration/badge.svg)](https://github.com/99designs/keyring/actions)
+[![Documentation](https://godoc.org/github.com/99designs/keyring?status.svg)](https://godoc.org/github.com/99designs/keyring)
+
+Keyring provides a common interface to a range of secure credential storage services. Originally developed as part of [AWS Vault](https://github.com/99designs/aws-vault), a command line tool for securely managing AWS access from developer workstations.
+
+Currently Keyring supports the following backends
+ * [macOS Keychain](https://support.apple.com/en-au/guide/keychain-access/welcome/mac)
+ * [Windows Credential Manager](https://support.microsoft.com/en-au/help/4026814/windows-accessing-credential-manager)
+ * Secret Service ([Gnome Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5))
+ * [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5)
+ * [Pass](https://www.passwordstore.org/)
+ * [Encrypted file (JWT)](https://datatracker.ietf.org/doc/html/rfc7519)
+ * [KeyCtl](https://linux.die.net/man/1/keyctl)
+
+
+## Usage
+
+The short version of how to use keyring is shown below.
+
+```go
+ring, _ := keyring.Open(keyring.Config{
+ ServiceName: "example",
+})
+
+_ = ring.Set(keyring.Item{
+ Key: "foo",
+ Data: []byte("secret-bar"),
+})
+
+i, _ := ring.Get("foo")
+
+fmt.Printf("%s", i.Data)
+```
+
+For more detail on the API please check [the keyring godocs](https://godoc.org/github.com/99designs/keyring)
+
+
+## Testing
+
+[Vagrant](https://www.vagrantup.com/) is used to create linux and windows test environments.
+
+```bash
+# Start vagrant
+vagrant up
+
+# Run go tests on all platforms
+./bin/go-test
+```
+
+
+## Contributing
+
+Contributions to the keyring package are most welcome from engineers of all backgrounds and skill levels. In particular the addition of extra backends across popular operating systems would be appreciated.
+
+This project will adhere to the [Go Community Code of Conduct](https://golang.org/conduct) in the github provided discussion spaces, with the moderators being the 99designs engineering team.
+
+To make a contribution:
+
+ * Fork the repository
+ * Make your changes on the fork
+ * Submit a pull request back to this repo with a clear description of the problem you're solving
+ * Ensure your PR passes all current (and new) tests
+ * Ideally verify that [aws-vault](https://github.com/99designs/aws-vault) works with your changes (optional)
+
+...and we'll do our best to get your work merged in
diff --git a/vendor/github.com/99designs/keyring/Vagrantfile b/vendor/github.com/99designs/keyring/Vagrantfile
new file mode 100644
index 0000000..7d30d2a
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/Vagrantfile
@@ -0,0 +1,85 @@
+Vagrant.configure("2") do |config|
+
+ config.vm.define "linux" do |linux|
+ linux.vm.box = "generic/fedora32"
+
+ linux.vm.provider "virtualbox" do |vb|
+ vb.gui = true
+ vb.memory = 2048
+ vb.cpus = 2
+
+ # VBoxVGA flickers constantly, use vmsvga instead which doesn't have that problem
+ vb.customize ["modifyvm", :id, "--graphicscontroller", "vmsvga"]
+ end
+
+ # mount the project into /keyring
+ linux.vm.synced_folder ".", "/keyring"
+
+ # install gnome desktop and auto login
+ linux.vm.provision "shell", inline: "sudo dnf install -y --exclude='gnome-initial-setup' @gnome-desktop langpacks-en"
+ linux.vm.provision "shell", inline: <<-SHELL
+ sudo sed -i -e 's/\\[daemon\\]/\\[daemon\\]\\nAutomaticLoginEnable=True\\nAutomaticLogin=vagrant\\n/' \
+ /etc/gdm/custom.conf
+ SHELL
+ linux.vm.provision "shell", inline: "sudo systemctl set-default graphical.target"
+ linux.vm.provision "shell", inline: "sudo systemctl isolate graphical.target"
+
+ # set the root password - sometimes prompts show up in gnome needing to install software
+ linux.vm.provision "shell", inline: "echo 'vagrant' | sudo passwd root --stdin"
+
+ # install gnome keyring
+ linux.vm.provision "shell", inline: "sudo dnf install -y gnome-keyring seahorse"
+
+ # install kwallet
+ linux.vm.provision "shell", inline: "sudo dnf install -y kwalletmanager5"
+
+ # install pass
+ linux.vm.provision "shell", inline: "sudo dnf install -y pass"
+
+ # install golang
+ linux.vm.provision "shell", inline: "sudo dnf install -y go"
+ end
+
+
+ config.vm.define "windows" do |windows|
+ windows.vm.box = "StefanScherer/windows_10"
+
+ windows.vm.provider "virtualbox" do |vb|
+ vb.gui = true
+ vb.memory = 2048
+ vb.cpus = 2
+ end
+
+ # mount the project into c:\keyring
+ windows.vm.synced_folder ".", "/keyring"
+
+ # install chocolately
+ windows.vm.provision "shell", privileged: true, inline: <<-SHELL
+ Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
+ choco feature disable -n=showDownloadProgress
+ SHELL
+
+ # install golang
+ windows.vm.provision "shell", privileged: true, inline: "choco install -y git golang"
+ end
+
+ config.vm.post_up_message = <<-MESSAGE
+ There are 2 vagrant boxes:
+ - linux
+ - OS: Fedora 32 with Gnome Desktop
+ - The keyring directory is mounted at /keyring
+ - Get a shell with 'vagrant ssh linux'
+ - When running go test, you'll need to use the GUI to click "Continue" on the prompts
+ - After provisioning, adjusting the virtualbox GUI window size doesn't cause the resolution to update. A 'vagrant reload linux' solves the problem
+ - windows
+ - OS: Windows 10
+ - The keyring directory is mounted at C:\keyring
+ - Get a shell by starting PowerShell in the GUI
+ - You can run commands remotely using 'vagrant winrm -e windows CMD'. You'll need the -e (elevated privileges) if you want to interact with wincred
+
+ Automated scripts for running go test on vagrant boxes (run these locally):
+ - ./bin/go-test-linux - Run tests on Linux
+ - ./bin/go-test-windows - Run tests on Windows
+ - ./bin/go-test - Run all tests - locally, linux and windows
+ MESSAGE
+end
diff --git a/vendor/github.com/99designs/keyring/array.go b/vendor/github.com/99designs/keyring/array.go
new file mode 100644
index 0000000..3179cb5
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/array.go
@@ -0,0 +1,54 @@
+package keyring
+
+// ArrayKeyring is a mock/non-secure backend that meets the Keyring interface.
+// It is intended to be used to aid unit testing of code that relies on the package.
+// NOTE: Do not use in production code.
+type ArrayKeyring struct {
+ items map[string]Item
+}
+
+// NewArrayKeyring returns an ArrayKeyring, optionally constructed with an initial slice
+// of items.
+func NewArrayKeyring(initial []Item) *ArrayKeyring {
+ kr := &ArrayKeyring{}
+ for _, i := range initial {
+ _ = kr.Set(i)
+ }
+ return kr
+}
+
+// Get returns an Item matching Key.
+func (k *ArrayKeyring) Get(key string) (Item, error) {
+ if i, ok := k.items[key]; ok {
+ return i, nil
+ }
+ return Item{}, ErrKeyNotFound
+}
+
+// Set will store an item on the mock Keyring.
+func (k *ArrayKeyring) Set(i Item) error {
+ if k.items == nil {
+ k.items = map[string]Item{}
+ }
+ k.items[i.Key] = i
+ return nil
+}
+
+// Remove will delete an Item from the Keyring.
+func (k *ArrayKeyring) Remove(key string) error {
+ delete(k.items, key)
+ return nil
+}
+
+// Keys provides a slice of all Item keys on the Keyring.
+func (k *ArrayKeyring) Keys() ([]string, error) {
+ var keys = []string{}
+ for key := range k.items {
+ keys = append(keys, key)
+ }
+ return keys, nil
+}
+
+func (k *ArrayKeyring) GetMetadata(_ string) (Metadata, error) {
+ return Metadata{}, ErrMetadataNeedsCredentials
+}
diff --git a/vendor/github.com/99designs/keyring/config.go b/vendor/github.com/99designs/keyring/config.go
new file mode 100644
index 0000000..590af7c
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/config.go
@@ -0,0 +1,58 @@
+package keyring
+
+// Config contains configuration for keyring.
+type Config struct {
+ // AllowedBackends is a whitelist of backend providers that can be used. Nil means all available.
+ AllowedBackends []BackendType
+
+ // ServiceName is a generic service name that is used by backends that support the concept
+ ServiceName string
+
+ // MacOSKeychainNameKeychainName is the name of the macOS keychain that is used
+ KeychainName string
+
+ // KeychainTrustApplication is whether the calling application should be trusted by default by items
+ KeychainTrustApplication bool
+
+ // KeychainSynchronizable is whether the item can be synchronized to iCloud
+ KeychainSynchronizable bool
+
+ // KeychainAccessibleWhenUnlocked is whether the item is accessible when the device is locked
+ KeychainAccessibleWhenUnlocked bool
+
+ // KeychainPasswordFunc is an optional function used to prompt the user for a password
+ KeychainPasswordFunc PromptFunc
+
+ // FilePasswordFunc is a required function used to prompt the user for a password
+ FilePasswordFunc PromptFunc
+
+ // FileDir is the directory that keyring files are stored in, ~/ is resolved to the users' home dir
+ FileDir string
+
+ // KeyCtlScope is the scope of the kernel keyring (either "user", "session", "process" or "thread")
+ KeyCtlScope string
+
+ // KeyCtlPerm is the permission mask to use for new keys
+ KeyCtlPerm uint32
+
+ // KWalletAppID is the application id for KWallet
+ KWalletAppID string
+
+ // KWalletFolder is the folder for KWallet
+ KWalletFolder string
+
+ // LibSecretCollectionName is the name collection in secret-service
+ LibSecretCollectionName string
+
+ // PassDir is the pass password-store directory, ~/ is resolved to the users' home dir
+ PassDir string
+
+ // PassCmd is the name of the pass executable
+ PassCmd string
+
+ // PassPrefix is a string prefix to prepend to the item path stored in pass
+ PassPrefix string
+
+ // WinCredPrefix is a string prefix to prepend to the key name
+ WinCredPrefix string
+}
diff --git a/vendor/github.com/99designs/keyring/docker-compose.yml b/vendor/github.com/99designs/keyring/docker-compose.yml
new file mode 100644
index 0000000..9020220
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/docker-compose.yml
@@ -0,0 +1,7 @@
+version: "3.9"
+services:
+ keyring:
+ image: golang:1.19
+ volumes:
+ - .:/usr/local/src/keyring
+ working_dir: /usr/local/src/keyring
diff --git a/vendor/github.com/99designs/keyring/file.go b/vendor/github.com/99designs/keyring/file.go
new file mode 100644
index 0000000..0d25b57
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/file.go
@@ -0,0 +1,180 @@
+package keyring
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "time"
+
+ jose "github.com/dvsekhvalnov/jose2go"
+ "github.com/mtibben/percent"
+)
+
+func init() {
+ supportedBackends[FileBackend] = opener(func(cfg Config) (Keyring, error) {
+ return &fileKeyring{
+ dir: cfg.FileDir,
+ passwordFunc: cfg.FilePasswordFunc,
+ }, nil
+ })
+}
+
+var filenameEscape = func(s string) string {
+ return percent.Encode(s, "/")
+}
+var filenameUnescape = percent.Decode
+
+type fileKeyring struct {
+ dir string
+ passwordFunc PromptFunc
+ password string
+}
+
+func (k *fileKeyring) resolveDir() (string, error) {
+ if k.dir == "" {
+ return "", fmt.Errorf("No directory provided for file keyring")
+ }
+
+ dir, err := ExpandTilde(k.dir)
+ if err != nil {
+ return "", err
+ }
+
+ stat, err := os.Stat(dir)
+ if os.IsNotExist(err) {
+ err = os.MkdirAll(dir, 0700)
+ } else if err != nil && stat != nil && !stat.IsDir() {
+ err = fmt.Errorf("%s is a file, not a directory", dir)
+ }
+
+ return dir, err
+}
+
+func (k *fileKeyring) unlock() error {
+ dir, err := k.resolveDir()
+ if err != nil {
+ return err
+ }
+
+ if k.password == "" {
+ pwd, err := k.passwordFunc(fmt.Sprintf("Enter passphrase to unlock %q", dir))
+ if err != nil {
+ return err
+ }
+ k.password = pwd
+ }
+
+ return nil
+}
+
+func (k *fileKeyring) Get(key string) (Item, error) {
+ filename, err := k.filename(key)
+ if err != nil {
+ return Item{}, err
+ }
+
+ bytes, err := os.ReadFile(filename)
+ if os.IsNotExist(err) {
+ return Item{}, ErrKeyNotFound
+ } else if err != nil {
+ return Item{}, err
+ }
+
+ if err = k.unlock(); err != nil {
+ return Item{}, err
+ }
+
+ payload, _, err := jose.Decode(string(bytes), k.password)
+ if err != nil {
+ return Item{}, err
+ }
+
+ var decoded Item
+ err = json.Unmarshal([]byte(payload), &decoded)
+
+ return decoded, err
+}
+
+func (k *fileKeyring) GetMetadata(key string) (Metadata, error) {
+ filename, err := k.filename(key)
+ if err != nil {
+ return Metadata{}, err
+ }
+
+ stat, err := os.Stat(filename)
+ if os.IsNotExist(err) {
+ return Metadata{}, ErrKeyNotFound
+ } else if err != nil {
+ return Metadata{}, err
+ }
+
+ // For the File provider, all internal data is encrypted, not just the
+ // credentials. Thus we only have the timestamps. Return a nil *Item.
+ //
+ // If we want to change this ... how portable are extended file attributes
+ // these days? Would it break user expectations of the security model to
+ // leak data into those? I'm hesitant to do so.
+
+ return Metadata{
+ ModificationTime: stat.ModTime(),
+ }, nil
+}
+
+func (k *fileKeyring) Set(i Item) error {
+ bytes, err := json.Marshal(i)
+ if err != nil {
+ return err
+ }
+
+ if err = k.unlock(); err != nil {
+ return err
+ }
+
+ token, err := jose.Encrypt(string(bytes), jose.PBES2_HS256_A128KW, jose.A256GCM, k.password,
+ jose.Headers(map[string]interface{}{
+ "created": time.Now().String(),
+ }))
+ if err != nil {
+ return err
+ }
+
+ filename, err := k.filename(i.Key)
+ if err != nil {
+ return err
+ }
+ return os.WriteFile(filename, []byte(token), 0600)
+}
+
+func (k *fileKeyring) filename(key string) (string, error) {
+ dir, err := k.resolveDir()
+ if err != nil {
+ return "", err
+ }
+
+ return filepath.Join(dir, filenameEscape(key)), nil
+}
+
+func (k *fileKeyring) Remove(key string) error {
+ filename, err := k.filename(key)
+ if err != nil {
+ return err
+ }
+
+ return os.Remove(filename)
+}
+
+func (k *fileKeyring) Keys() ([]string, error) {
+ dir, err := k.resolveDir()
+ if err != nil {
+ return nil, err
+ }
+
+ var keys = []string{}
+ files, _ := os.ReadDir(dir)
+ for _, f := range files {
+ keys = append(keys, filenameUnescape(f.Name()))
+ }
+
+ return keys, nil
+}
diff --git a/vendor/github.com/99designs/keyring/keychain.go b/vendor/github.com/99designs/keyring/keychain.go
new file mode 100644
index 0000000..d4e634e
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/keychain.go
@@ -0,0 +1,301 @@
+//go:build darwin && cgo
+// +build darwin,cgo
+
+package keyring
+
+import (
+ "errors"
+ "fmt"
+
+ gokeychain "github.com/99designs/go-keychain"
+)
+
+type keychain struct {
+ path string
+ service string
+
+ passwordFunc PromptFunc
+
+ isSynchronizable bool
+ isAccessibleWhenUnlocked bool
+ isTrusted bool
+}
+
+func init() {
+ supportedBackends[KeychainBackend] = opener(func(cfg Config) (Keyring, error) {
+ kc := &keychain{
+ service: cfg.ServiceName,
+ passwordFunc: cfg.KeychainPasswordFunc,
+
+ // Set the isAccessibleWhenUnlocked to the boolean value of
+ // KeychainAccessibleWhenUnlocked is a shorthand for setting the accessibility value.
+ // See: https://developer.apple.com/documentation/security/ksecattraccessiblewhenunlocked
+ isAccessibleWhenUnlocked: cfg.KeychainAccessibleWhenUnlocked,
+ }
+ if cfg.KeychainName != "" {
+ kc.path = cfg.KeychainName + ".keychain"
+ }
+ if cfg.KeychainTrustApplication {
+ kc.isTrusted = true
+ }
+ return kc, nil
+ })
+}
+
+func (k *keychain) Get(key string) (Item, error) {
+ query := gokeychain.NewItem()
+ query.SetSecClass(gokeychain.SecClassGenericPassword)
+ query.SetService(k.service)
+ query.SetAccount(key)
+ query.SetMatchLimit(gokeychain.MatchLimitOne)
+ query.SetReturnAttributes(true)
+ query.SetReturnData(true)
+
+ if k.path != "" {
+ // When we are querying, we don't create by default
+ query.SetMatchSearchList(gokeychain.NewWithPath(k.path))
+ }
+
+ debugf("Querying keychain for service=%q, account=%q, keychain=%q", k.service, key, k.path)
+ results, err := gokeychain.QueryItem(query)
+ if err == gokeychain.ErrorItemNotFound || len(results) == 0 {
+ debugf("No results found")
+ return Item{}, ErrKeyNotFound
+ }
+
+ if err != nil {
+ debugf("Error: %#v", err)
+ return Item{}, err
+ }
+
+ item := Item{
+ Key: key,
+ Data: results[0].Data,
+ Label: results[0].Label,
+ Description: results[0].Description,
+ }
+
+ debugf("Found item %q", results[0].Label)
+ return item, nil
+}
+
+func (k *keychain) GetMetadata(key string) (Metadata, error) {
+ query := gokeychain.NewItem()
+ query.SetSecClass(gokeychain.SecClassGenericPassword)
+ query.SetService(k.service)
+ query.SetAccount(key)
+ query.SetMatchLimit(gokeychain.MatchLimitOne)
+ query.SetReturnAttributes(true)
+ query.SetReturnData(false)
+ query.SetReturnRef(true)
+
+ debugf("Querying keychain for metadata of service=%q, account=%q, keychain=%q", k.service, key, k.path)
+ results, err := gokeychain.QueryItem(query)
+ if err == gokeychain.ErrorItemNotFound || len(results) == 0 {
+ debugf("No results found")
+ return Metadata{}, ErrKeyNotFound
+ } else if err != nil {
+ debugf("Error: %#v", err)
+ return Metadata{}, err
+ }
+
+ md := Metadata{
+ Item: &Item{
+ Key: key,
+ Label: results[0].Label,
+ Description: results[0].Description,
+ },
+ ModificationTime: results[0].ModificationDate,
+ }
+
+ debugf("Found metadata for %q", md.Item.Label)
+
+ return md, nil
+}
+
+func (k *keychain) updateItem(kc gokeychain.Keychain, kcItem gokeychain.Item, account string) error {
+ queryItem := gokeychain.NewItem()
+ queryItem.SetSecClass(gokeychain.SecClassGenericPassword)
+ queryItem.SetService(k.service)
+ queryItem.SetAccount(account)
+ queryItem.SetMatchLimit(gokeychain.MatchLimitOne)
+ queryItem.SetReturnAttributes(true)
+
+ if k.path != "" {
+ queryItem.SetMatchSearchList(kc)
+ }
+
+ results, err := gokeychain.QueryItem(queryItem)
+ if err != nil {
+ return fmt.Errorf("Failed to query keychain: %v", err)
+ }
+ if len(results) == 0 {
+ return errors.New("no results")
+ }
+
+ // Don't call SetAccess() as this will cause multiple prompts on update, even when we are not updating the AccessList
+ kcItem.SetAccess(nil)
+
+ if err := gokeychain.UpdateItem(queryItem, kcItem); err != nil {
+ return fmt.Errorf("Failed to update item in keychain: %v", err)
+ }
+
+ return nil
+}
+
+func (k *keychain) Set(item Item) error {
+ var kc gokeychain.Keychain
+
+ // when we are setting a value, we create or open
+ if k.path != "" {
+ var err error
+ kc, err = k.createOrOpen()
+ if err != nil {
+ return err
+ }
+ }
+
+ kcItem := gokeychain.NewItem()
+ kcItem.SetSecClass(gokeychain.SecClassGenericPassword)
+ kcItem.SetService(k.service)
+ kcItem.SetAccount(item.Key)
+ kcItem.SetLabel(item.Label)
+ kcItem.SetDescription(item.Description)
+ kcItem.SetData(item.Data)
+
+ if k.path != "" {
+ kcItem.UseKeychain(kc)
+ }
+
+ if k.isSynchronizable && !item.KeychainNotSynchronizable {
+ kcItem.SetSynchronizable(gokeychain.SynchronizableYes)
+ }
+
+ if k.isAccessibleWhenUnlocked {
+ kcItem.SetAccessible(gokeychain.AccessibleWhenUnlocked)
+ }
+
+ isTrusted := k.isTrusted && !item.KeychainNotTrustApplication
+
+ if isTrusted {
+ debugf("Keychain item trusts keyring")
+ kcItem.SetAccess(&gokeychain.Access{
+ Label: item.Label,
+ TrustedApplications: nil,
+ })
+ } else {
+ debugf("Keychain item doesn't trust keyring")
+ kcItem.SetAccess(&gokeychain.Access{
+ Label: item.Label,
+ TrustedApplications: []string{},
+ })
+ }
+
+ debugf("Adding service=%q, label=%q, account=%q, trusted=%v to osx keychain %q", k.service, item.Label, item.Key, isTrusted, k.path)
+
+ err := gokeychain.AddItem(kcItem)
+
+ if err == gokeychain.ErrorDuplicateItem {
+ debugf("Item already exists, updating")
+ err = k.updateItem(kc, kcItem, item.Key)
+ }
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (k *keychain) Remove(key string) error {
+ item := gokeychain.NewItem()
+ item.SetSecClass(gokeychain.SecClassGenericPassword)
+ item.SetService(k.service)
+ item.SetAccount(key)
+
+ if k.path != "" {
+ kc := gokeychain.NewWithPath(k.path)
+
+ if err := kc.Status(); err != nil {
+ if err == gokeychain.ErrorNoSuchKeychain {
+ return ErrKeyNotFound
+ }
+ return err
+ }
+
+ item.SetMatchSearchList(kc)
+ }
+
+ debugf("Removing keychain item service=%q, account=%q, keychain %q", k.service, key, k.path)
+ err := gokeychain.DeleteItem(item)
+ if err == gokeychain.ErrorItemNotFound {
+ return ErrKeyNotFound
+ }
+
+ return err
+}
+
+func (k *keychain) Keys() ([]string, error) {
+ query := gokeychain.NewItem()
+ query.SetSecClass(gokeychain.SecClassGenericPassword)
+ query.SetService(k.service)
+ query.SetMatchLimit(gokeychain.MatchLimitAll)
+ query.SetReturnAttributes(true)
+
+ if k.path != "" {
+ kc := gokeychain.NewWithPath(k.path)
+
+ if err := kc.Status(); err != nil {
+ if err == gokeychain.ErrorNoSuchKeychain {
+ return []string{}, nil
+ }
+ return nil, err
+ }
+
+ query.SetMatchSearchList(kc)
+ }
+
+ debugf("Querying keychain for service=%q, keychain=%q", k.service, k.path)
+ results, err := gokeychain.QueryItem(query)
+ if err != nil {
+ return nil, err
+ }
+
+ debugf("Found %d results", len(results))
+ accountNames := make([]string, len(results))
+ for idx, r := range results {
+ accountNames[idx] = r.Account
+ }
+
+ return accountNames, nil
+}
+
+func (k *keychain) createOrOpen() (gokeychain.Keychain, error) {
+ kc := gokeychain.NewWithPath(k.path)
+
+ debugf("Checking keychain status")
+ err := kc.Status()
+ if err == nil {
+ debugf("Keychain status returned nil, keychain exists")
+ return kc, nil
+ }
+
+ debugf("Keychain status returned error: %v", err)
+
+ if err != gokeychain.ErrorNoSuchKeychain {
+ return gokeychain.Keychain{}, err
+ }
+
+ if k.passwordFunc == nil {
+ debugf("Creating keychain %s with prompt", k.path)
+ return gokeychain.NewKeychainWithPrompt(k.path)
+ }
+
+ passphrase, err := k.passwordFunc("Enter passphrase for keychain")
+ if err != nil {
+ return gokeychain.Keychain{}, err
+ }
+
+ debugf("Creating keychain %s with provided password", k.path)
+ return gokeychain.NewKeychain(k.path, passphrase)
+}
diff --git a/vendor/github.com/99designs/keyring/keyctl.go b/vendor/github.com/99designs/keyring/keyctl.go
new file mode 100644
index 0000000..bd8018a
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/keyctl.go
@@ -0,0 +1,327 @@
+//go:build linux
+// +build linux
+
+package keyring
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+//nolint:revive
+const (
+ KEYCTL_PERM_VIEW = uint32(1 << 0)
+ KEYCTL_PERM_READ = uint32(1 << 1)
+ KEYCTL_PERM_WRITE = uint32(1 << 2)
+ KEYCTL_PERM_SEARCH = uint32(1 << 3)
+ KEYCTL_PERM_LINK = uint32(1 << 4)
+ KEYCTL_PERM_SETATTR = uint32(1 << 5)
+ KEYCTL_PERM_ALL = uint32((1 << 6) - 1)
+
+ KEYCTL_PERM_OTHERS = 0
+ KEYCTL_PERM_GROUP = 8
+ KEYCTL_PERM_USER = 16
+ KEYCTL_PERM_PROCESS = 24
+)
+
+// GetPermissions constructs the permission mask from the elements.
+func GetPermissions(process, user, group, others uint32) uint32 {
+ perm := others << KEYCTL_PERM_OTHERS
+ perm |= group << KEYCTL_PERM_GROUP
+ perm |= user << KEYCTL_PERM_USER
+ perm |= process << KEYCTL_PERM_PROCESS
+
+ return perm
+}
+
+// GetKeyringIDForScope get the keyring ID for a given scope.
+func GetKeyringIDForScope(scope string) (int32, error) {
+ ringRef, err := getKeyringForScope(scope)
+ if err != nil {
+ return 0, err
+ }
+ id, err := unix.KeyctlGetKeyringID(int(ringRef), false)
+ return int32(id), err
+}
+
+type keyctlKeyring struct {
+ keyring int32
+ perm uint32
+}
+
+func init() {
+ supportedBackends[KeyCtlBackend] = opener(func(cfg Config) (Keyring, error) {
+ keyring := keyctlKeyring{}
+ if cfg.KeyCtlPerm > 0 {
+ keyring.perm = cfg.KeyCtlPerm
+ }
+
+ parent, err := getKeyringForScope(cfg.KeyCtlScope)
+ if err != nil {
+ return nil, fmt.Errorf("accessing %q keyring failed: %v", cfg.KeyCtlScope, err)
+ }
+
+ // Check for named keyrings
+ keyring.keyring = parent
+ if cfg.ServiceName != "" {
+ namedKeyring, err := keyctlSearch(parent, "keyring", cfg.ServiceName)
+ if err != nil {
+ if !errors.Is(err, syscall.ENOKEY) {
+ return nil, fmt.Errorf("opening named %q keyring failed: %v", cfg.KeyCtlScope, err)
+ }
+
+ // Keyring does not yet exist, create it
+ namedKeyring, err = keyring.createNamedKeyring(parent, cfg.ServiceName)
+ if err != nil {
+ return nil, fmt.Errorf("creating named %q keyring failed: %v", cfg.KeyCtlScope, err)
+ }
+ }
+ keyring.keyring = namedKeyring
+ }
+
+ return &keyring, nil
+ })
+}
+
+func (k *keyctlKeyring) Get(name string) (Item, error) {
+ key, err := keyctlSearch(k.keyring, "user", name)
+ if err != nil {
+ if errors.Is(err, syscall.ENOKEY) {
+ return Item{}, ErrKeyNotFound
+ }
+ return Item{}, err
+ }
+ // data, err := key.Get()
+ data, err := keyctlRead(key)
+ if err != nil {
+ return Item{}, err
+ }
+
+ item := Item{
+ Key: name,
+ Data: data,
+ }
+
+ return item, nil
+}
+
+// GetMetadata for pass returns an error indicating that it's unsupported for this backend.
+// TODO: We can deliver metadata different from the defined ones (e.g. permissions, expire-time, etc).
+func (k *keyctlKeyring) GetMetadata(_ string) (Metadata, error) {
+ return Metadata{}, ErrMetadataNotSupported
+}
+
+func (k *keyctlKeyring) Set(item Item) error {
+ if k.perm == 0 {
+ // Keep the default permissions (alswrv-----v------------)
+ _, err := keyctlAdd(k.keyring, "user", item.Key, item.Data)
+ return err
+ }
+
+ // By default we loose possession of the key in anything above the session keyring.
+ // Together with the default permissions (which cannot be changed during creation) we
+ // cannot change the permissions without possessing the key. Therefore, create the
+ // key in the session keyring, change permissions and then link to the target
+ // keyring and unlink from the intermediate keyring again.
+ key, err := keyctlAdd(unix.KEY_SPEC_SESSION_KEYRING, "user", item.Key, item.Data)
+ if err != nil {
+ return fmt.Errorf("adding key to session failed: %v", err)
+ }
+
+ if err := keyctlSetperm(key, k.perm); err != nil {
+ return fmt.Errorf("setting permission 0x%x failed: %v", k.perm, err)
+ }
+
+ if err := keyctlLink(k.keyring, key); err != nil {
+ return fmt.Errorf("linking key to keyring failed: %v", err)
+ }
+
+ if err := keyctlUnlink(unix.KEY_SPEC_SESSION_KEYRING, key); err != nil {
+ return fmt.Errorf("unlinking key from session failed: %v", err)
+ }
+
+ return nil
+}
+
+func (k *keyctlKeyring) Remove(name string) error {
+ key, err := keyctlSearch(k.keyring, "user", name)
+ if err != nil {
+ return ErrKeyNotFound
+ }
+
+ return keyctlUnlink(k.keyring, key)
+}
+
+func (k *keyctlKeyring) Keys() ([]string, error) {
+ results := []string{}
+
+ data, err := keyctlRead(k.keyring)
+ if err != nil {
+ return nil, fmt.Errorf("reading keyring failed: %v", err)
+ }
+ ids, err := keyctlConvertKeyBuffer(data)
+ if err != nil {
+ return nil, fmt.Errorf("converting raw keylist failed: %v", err)
+ }
+
+ for _, id := range ids {
+ info, err := keyctlDescribe(id)
+ if err != nil {
+ return nil, err
+ }
+ if info["type"] == "user" {
+ results = append(results, info["description"])
+ }
+ }
+
+ return results, nil
+}
+
+func (k *keyctlKeyring) createNamedKeyring(parent int32, name string) (int32, error) {
+ if k.perm == 0 {
+ // Keep the default permissions (alswrv-----v------------)
+ return keyctlAdd(parent, "keyring", name, nil)
+ }
+
+ // By default we loose possession of the keyring in anything above the session keyring.
+ // Together with the default permissions (which cannot be changed during creation) we
+ // cannot change the permissions without possessing the keyring. Therefore, create the
+ // keyring linked to the session keyring, change permissions and then link to the target
+ // keyring and unlink from the intermediate keyring again.
+ keyring, err := keyctlAdd(unix.KEY_SPEC_SESSION_KEYRING, "keyring", name, nil)
+ if err != nil {
+ return 0, fmt.Errorf("creating keyring failed: %v", err)
+ }
+
+ if err := keyctlSetperm(keyring, k.perm); err != nil {
+ return 0, fmt.Errorf("setting permission 0x%x failed: %v", k.perm, err)
+ }
+
+ if err := keyctlLink(k.keyring, keyring); err != nil {
+ return 0, fmt.Errorf("linking keyring failed: %v", err)
+ }
+
+ if err := keyctlUnlink(unix.KEY_SPEC_SESSION_KEYRING, keyring); err != nil {
+ return 0, fmt.Errorf("unlinking keyring from session failed: %v", err)
+ }
+
+ return keyring, nil
+}
+
+func getKeyringForScope(scope string) (int32, error) {
+ switch scope {
+ case "user":
+ return int32(unix.KEY_SPEC_USER_KEYRING), nil
+ case "usersession":
+ return int32(unix.KEY_SPEC_USER_SESSION_KEYRING), nil
+ case "group":
+ // Not yet implemented in the kernel
+ // return int32(unix.KEY_SPEC_GROUP_KEYRING)
+ return 0, fmt.Errorf("scope %q not yet implemented", scope)
+ case "session":
+ return int32(unix.KEY_SPEC_SESSION_KEYRING), nil
+ case "process":
+ return int32(unix.KEY_SPEC_PROCESS_KEYRING), nil
+ case "thread":
+ return int32(unix.KEY_SPEC_THREAD_KEYRING), nil
+ }
+ return 0, fmt.Errorf("unknown scope %q", scope)
+}
+
+func keyctlAdd(parent int32, keytype, key string, data []byte) (int32, error) {
+ id, err := unix.AddKey(keytype, key, data, int(parent))
+ if err != nil {
+ return 0, err
+ }
+ return int32(id), nil
+}
+
+func keyctlSearch(id int32, idtype, name string) (int32, error) {
+ key, err := unix.KeyctlSearch(int(id), idtype, name, 0)
+ if err != nil {
+ return 0, err
+ }
+ return int32(key), nil
+}
+
+func keyctlRead(id int32) ([]byte, error) {
+ var buffer []byte
+
+ for {
+ length, err := unix.KeyctlBuffer(unix.KEYCTL_READ, int(id), buffer, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ // Return the buffer if it was large enough
+ if length <= len(buffer) {
+ return buffer[:length], nil
+ }
+
+ // Next try with a larger buffer
+ buffer = make([]byte, length)
+ }
+}
+
+func keyctlDescribe(id int32) (map[string]string, error) {
+ description, err := unix.KeyctlString(unix.KEYCTL_DESCRIBE, int(id))
+ if err != nil {
+ return nil, err
+ }
+ fields := strings.Split(description, ";")
+ if len(fields) < 1 {
+ return nil, fmt.Errorf("no data")
+ }
+
+ data := make(map[string]string)
+ names := []string{"type", "uid", "gid", "perm"} // according to keyctlDescribe(3) new fields are added at the end
+ data["description"] = fields[len(fields)-1] // according to keyctlDescribe(3) description is always last
+ for i, f := range fields[:len(fields)-1] {
+ if i >= len(names) {
+ // Do not stumble upon unknown fields
+ break
+ }
+ data[names[i]] = f
+ }
+
+ return data, nil
+}
+
+func keyctlLink(parent, child int32) error {
+ _, _, errno := syscall.Syscall(syscall.SYS_KEYCTL, uintptr(unix.KEYCTL_LINK), uintptr(child), uintptr(parent))
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
+
+func keyctlUnlink(parent, child int32) error {
+ _, _, errno := syscall.Syscall(syscall.SYS_KEYCTL, uintptr(unix.KEYCTL_UNLINK), uintptr(child), uintptr(parent))
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
+
+func keyctlSetperm(id int32, perm uint32) error {
+ return unix.KeyctlSetperm(int(id), perm)
+}
+
+func keyctlConvertKeyBuffer(buffer []byte) ([]int32, error) {
+ if len(buffer)%4 != 0 {
+ return nil, fmt.Errorf("buffer size %d not a multiple of 4", len(buffer))
+ }
+
+ results := make([]int32, 0, len(buffer)/4)
+ for i := 0; i < len(buffer); i += 4 {
+ // We need to case in host-native endianess here as this is what we get from the kernel.
+ r := *((*int32)(unsafe.Pointer(&buffer[i])))
+ results = append(results, r)
+ }
+ return results, nil
+}
diff --git a/vendor/github.com/99designs/keyring/keyring.go b/vendor/github.com/99designs/keyring/keyring.go
new file mode 100644
index 0000000..12161b7
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/keyring.go
@@ -0,0 +1,134 @@
+// Package keyring provides a uniform API over a range of desktop credential storage engines.
+package keyring
+
+import (
+ "errors"
+ "log"
+ "time"
+)
+
+// BackendType is an identifier for a credential storage service.
+type BackendType string
+
+// All currently supported secure storage backends.
+const (
+ InvalidBackend BackendType = ""
+ SecretServiceBackend BackendType = "secret-service"
+ KeychainBackend BackendType = "keychain"
+ KeyCtlBackend BackendType = "keyctl"
+ KWalletBackend BackendType = "kwallet"
+ WinCredBackend BackendType = "wincred"
+ FileBackend BackendType = "file"
+ PassBackend BackendType = "pass"
+)
+
+// This order makes sure the OS-specific backends
+// are picked over the more generic backends.
+var backendOrder = []BackendType{
+ // Windows
+ WinCredBackend,
+ // MacOS
+ KeychainBackend,
+ // Linux
+ SecretServiceBackend,
+ KWalletBackend,
+ KeyCtlBackend,
+ // General
+ PassBackend,
+ FileBackend,
+}
+
+var supportedBackends = map[BackendType]opener{}
+
+// AvailableBackends provides a slice of all available backend keys on the current OS.
+func AvailableBackends() []BackendType {
+ b := []BackendType{}
+ for _, k := range backendOrder {
+ _, ok := supportedBackends[k]
+ if ok {
+ b = append(b, k)
+ }
+ }
+ return b
+}
+
+type opener func(cfg Config) (Keyring, error)
+
+// Open will open a specific keyring backend.
+func Open(cfg Config) (Keyring, error) {
+ if cfg.AllowedBackends == nil {
+ cfg.AllowedBackends = AvailableBackends()
+ }
+ debugf("Considering backends: %v", cfg.AllowedBackends)
+ for _, backend := range cfg.AllowedBackends {
+ if opener, ok := supportedBackends[backend]; ok {
+ openBackend, err := opener(cfg)
+ if err != nil {
+ debugf("Failed backend %s: %s", backend, err)
+ continue
+ }
+ return openBackend, nil
+ }
+ }
+ return nil, ErrNoAvailImpl
+}
+
+// Item is a thing stored on the keyring.
+type Item struct {
+ Key string
+ Data []byte
+ Label string
+ Description string
+
+ // Backend specific config
+ KeychainNotTrustApplication bool
+ KeychainNotSynchronizable bool
+}
+
+// Metadata is information about a thing stored on the keyring; retrieving
+// metadata must not require authentication. The embedded Item should be
+// filled in with an empty Data field.
+// It's allowed for Item to be a nil pointer, indicating that all we
+// have is the timestamps.
+type Metadata struct {
+ *Item
+ ModificationTime time.Time
+}
+
+// Keyring provides the uniform interface over the underlying backends.
+type Keyring interface {
+ // Returns an Item matching the key or ErrKeyNotFound
+ Get(key string) (Item, error)
+ // Returns the non-secret parts of an Item
+ GetMetadata(key string) (Metadata, error)
+ // Stores an Item on the keyring
+ Set(item Item) error
+ // Removes the item with matching key
+ Remove(key string) error
+ // Provides a slice of all keys stored on the keyring
+ Keys() ([]string, error)
+}
+
+// ErrNoAvailImpl is returned by Open when a backend cannot be found.
+var ErrNoAvailImpl = errors.New("Specified keyring backend not available")
+
+// ErrKeyNotFound is returned by Keyring Get when the item is not on the keyring.
+var ErrKeyNotFound = errors.New("The specified item could not be found in the keyring")
+
+// ErrMetadataNeedsCredentials is returned when Metadata is called against a
+// backend which requires credentials even to see metadata.
+var ErrMetadataNeedsCredentials = errors.New("The keyring backend requires credentials for metadata access")
+
+// ErrMetadataNotSupported is returned when Metadata is not available for the backend.
+var ErrMetadataNotSupported = errors.New("The keyring backend does not support metadata access")
+
+var (
+ // Debug specifies whether to print debugging output.
+ Debug bool
+)
+
+func debugf(pattern string, args ...interface{}) {
+ if Debug {
+ log.Printf("[keyring] "+pattern, args...)
+ }
+}
diff --git a/vendor/github.com/99designs/keyring/kwallet.go b/vendor/github.com/99designs/keyring/kwallet.go
new file mode 100644
index 0000000..771baba
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/kwallet.go
@@ -0,0 +1,237 @@
+//go:build linux
+// +build linux
+
+package keyring
+
+import (
+ "encoding/json"
+ "os"
+
+ "github.com/godbus/dbus"
+)
+
+const (
+ dbusServiceName = "org.kde.kwalletd5"
+ dbusPath = "/modules/kwalletd5"
+)
+
+func init() {
+ if os.Getenv("DISABLE_KWALLET") == "1" {
+ return
+ }
+
+ // silently fail if dbus isn't available
+ _, err := dbus.SessionBus()
+ if err != nil {
+ return
+ }
+
+ supportedBackends[KWalletBackend] = opener(func(cfg Config) (Keyring, error) {
+ if cfg.ServiceName == "" {
+ cfg.ServiceName = "kdewallet"
+ }
+
+ if cfg.KWalletAppID == "" {
+ cfg.KWalletAppID = "keyring"
+ }
+
+ if cfg.KWalletFolder == "" {
+ cfg.KWalletFolder = "keyring"
+ }
+
+ wallet, err := newKwallet()
+ if err != nil {
+ return nil, err
+ }
+
+ ring := &kwalletKeyring{
+ wallet: *wallet,
+ name: cfg.ServiceName,
+ appID: cfg.KWalletAppID,
+ folder: cfg.KWalletFolder,
+ }
+
+ return ring, ring.openWallet()
+ })
+}
+
+type kwalletKeyring struct {
+ wallet kwalletBinding
+ name string
+ handle int32
+ appID string
+ folder string
+}
+
+func (k *kwalletKeyring) openWallet() error {
+ isOpen, err := k.wallet.IsOpen(k.handle)
+ if err != nil {
+ return err
+ }
+
+ if !isOpen {
+ handle, err := k.wallet.Open(k.name, 0, k.appID)
+ if err != nil {
+ return err
+ }
+ k.handle = handle
+ }
+
+ return nil
+}
+
+func (k *kwalletKeyring) Get(key string) (Item, error) {
+ err := k.openWallet()
+ if err != nil {
+ return Item{}, err
+ }
+
+ data, err := k.wallet.ReadEntry(k.handle, k.folder, key, k.appID)
+ if err != nil {
+ return Item{}, err
+ }
+ if len(data) == 0 {
+ return Item{}, ErrKeyNotFound
+ }
+
+ item := Item{}
+ err = json.Unmarshal(data, &item)
+ if err != nil {
+ return Item{}, err
+ }
+
+ return item, nil
+}
+
+// GetMetadata for kwallet returns an error indicating that it's unsupported
+// for this backend.
+//
+// The only APIs found around KWallet are for retrieving content, no indication
+// found in docs for methods to use to retrieve metadata without needing unlock
+// credentials.
+func (k *kwalletKeyring) GetMetadata(_ string) (Metadata, error) {
+ return Metadata{}, ErrMetadataNeedsCredentials
+}
+
+func (k *kwalletKeyring) Set(item Item) error {
+ err := k.openWallet()
+ if err != nil {
+ return err
+ }
+
+ data, err := json.Marshal(item)
+ if err != nil {
+ return err
+ }
+
+ err = k.wallet.WriteEntry(k.handle, k.folder, item.Key, data, k.appID)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (k *kwalletKeyring) Remove(key string) error {
+ err := k.openWallet()
+ if err != nil {
+ return err
+ }
+
+ err = k.wallet.RemoveEntry(k.handle, k.folder, key, k.appID)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (k *kwalletKeyring) Keys() ([]string, error) {
+ err := k.openWallet()
+ if err != nil {
+ return []string{}, err
+ }
+
+ entries, err := k.wallet.EntryList(k.handle, k.folder, k.appID)
+ if err != nil {
+ return []string{}, err
+ }
+
+ return entries, nil
+}
+
+func newKwallet() (*kwalletBinding, error) {
+ conn, err := dbus.SessionBus()
+ if err != nil {
+ return nil, err
+ }
+
+ return &kwalletBinding{
+ conn.Object(dbusServiceName, dbusPath),
+ }, nil
+}
+
+// Dumb Dbus bindings for kwallet bindings with types.
+type kwalletBinding struct {
+ dbus dbus.BusObject
+}
+
+// method bool org.kde.KWallet.isOpen(int handle)
+func (k *kwalletBinding) IsOpen(handle int32) (bool, error) {
+ call := k.dbus.Call("org.kde.KWallet.isOpen", 0, handle)
+ if call.Err != nil {
+ return false, call.Err
+ }
+
+ return call.Body[0].(bool), call.Err
+}
+
+// method int org.kde.KWallet.open(QString wallet, qlonglong wId, QString appid)
+func (k *kwalletBinding) Open(name string, wID int64, appid string) (int32, error) {
+ call := k.dbus.Call("org.kde.KWallet.open", 0, name, wID, appid)
+ if call.Err != nil {
+ return 0, call.Err
+ }
+
+ return call.Body[0].(int32), call.Err
+}
+
+// method QStringList org.kde.KWallet.entryList(int handle, QString folder, QString appid)
+func (k *kwalletBinding) EntryList(handle int32, folder string, appid string) ([]string, error) {
+ call := k.dbus.Call("org.kde.KWallet.entryList", 0, handle, folder, appid)
+ if call.Err != nil {
+ return []string{}, call.Err
+ }
+
+ return call.Body[0].([]string), call.Err
+}
+
+// method int org.kde.KWallet.writeEntry(int handle, QString folder, QString key, QByteArray value, QString appid)
+func (k *kwalletBinding) WriteEntry(handle int32, folder string, key string, value []byte, appid string) error {
+ call := k.dbus.Call("org.kde.KWallet.writeEntry", 0, handle, folder, key, value, appid)
+ if call.Err != nil {
+ return call.Err
+ }
+
+ return call.Err
+}
+
+// method int org.kde.KWallet.removeEntry(int handle, QString folder, QString key, QString appid)
+func (k *kwalletBinding) RemoveEntry(handle int32, folder string, key string, appid string) error {
+ call := k.dbus.Call("org.kde.KWallet.removeEntry", 0, handle, folder, key, appid)
+ if call.Err != nil {
+ return call.Err
+ }
+
+ return call.Err
+}
+
+// method QByteArray org.kde.KWallet.readEntry(int handle, QString folder, QString key, QString appid)
+func (k *kwalletBinding) ReadEntry(handle int32, folder string, key string, appid string) ([]byte, error) {
+ call := k.dbus.Call("org.kde.KWallet.readEntry", 0, handle, folder, key, appid)
+ if call.Err != nil {
+ return []byte{}, call.Err
+ }
+
+ return call.Body[0].([]byte), call.Err
+}
diff --git a/vendor/github.com/99designs/keyring/pass.go b/vendor/github.com/99designs/keyring/pass.go
new file mode 100644
index 0000000..5ca0bf0
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/pass.go
@@ -0,0 +1,166 @@
+//go:build !windows
+// +build !windows
+
+package keyring
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+func init() {
+ supportedBackends[PassBackend] = opener(func(cfg Config) (Keyring, error) {
+ var err error
+
+ pass := &passKeyring{
+ passcmd: cfg.PassCmd,
+ dir: cfg.PassDir,
+ prefix: cfg.PassPrefix,
+ }
+
+ if pass.passcmd == "" {
+ pass.passcmd = "pass"
+ }
+
+ if pass.dir == "" {
+ if passDir, found := os.LookupEnv("PASSWORD_STORE_DIR"); found {
+ pass.dir = passDir
+ } else {
+ homeDir, err := os.UserHomeDir()
+ if err != nil {
+ return nil, err
+ }
+ pass.dir = filepath.Join(homeDir, ".password-store")
+ }
+ }
+
+ pass.dir, err = ExpandTilde(pass.dir)
+ if err != nil {
+ return nil, err
+ }
+
+ // fail if the pass program is not available
+ _, err = exec.LookPath(pass.passcmd)
+ if err != nil {
+ return nil, errors.New("The pass program is not available")
+ }
+
+ return pass, nil
+ })
+}
+
+type passKeyring struct {
+ dir string
+ passcmd string
+ prefix string
+}
+
+func (k *passKeyring) pass(args ...string) *exec.Cmd {
+ cmd := exec.Command(k.passcmd, args...)
+ if k.dir != "" {
+ cmd.Env = append(os.Environ(), fmt.Sprintf("PASSWORD_STORE_DIR=%s", k.dir))
+ }
+ cmd.Stderr = os.Stderr
+
+ return cmd
+}
+
+func (k *passKeyring) Get(key string) (Item, error) {
+ if !k.itemExists(key) {
+ return Item{}, ErrKeyNotFound
+ }
+
+ name := filepath.Join(k.prefix, key)
+ cmd := k.pass("show", name)
+ output, err := cmd.Output()
+ if err != nil {
+ return Item{}, err
+ }
+
+ var decoded Item
+ err = json.Unmarshal(output, &decoded)
+
+ return decoded, err
+}
+
+func (k *passKeyring) GetMetadata(key string) (Metadata, error) {
+ return Metadata{}, nil
+}
+
+func (k *passKeyring) Set(i Item) error {
+ bytes, err := json.Marshal(i)
+ if err != nil {
+ return err
+ }
+
+ name := filepath.Join(k.prefix, i.Key)
+ cmd := k.pass("insert", "-m", "-f", name)
+ cmd.Stdin = strings.NewReader(string(bytes))
+
+ err = cmd.Run()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (k *passKeyring) Remove(key string) error {
+ if !k.itemExists(key) {
+ return ErrKeyNotFound
+ }
+
+ name := filepath.Join(k.prefix, key)
+ cmd := k.pass("rm", "-f", name)
+ err := cmd.Run()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (k *passKeyring) itemExists(key string) bool {
+ var path = filepath.Join(k.dir, k.prefix, key+".gpg")
+ _, err := os.Stat(path)
+
+ return err == nil
+}
+
+func (k *passKeyring) Keys() ([]string, error) {
+ var keys = []string{}
+ var path = filepath.Join(k.dir, k.prefix)
+
+ info, err := os.Stat(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return keys, nil
+ }
+ return keys, err
+ }
+ if !info.IsDir() {
+ return keys, fmt.Errorf("%s is not a directory", path)
+ }
+
+ err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ if !info.IsDir() && filepath.Ext(p) == ".gpg" {
+ name := strings.TrimPrefix(p, path)
+ if name[0] == os.PathSeparator {
+ name = name[1:]
+ }
+ keys = append(keys, name[:len(name)-4])
+ }
+ return nil
+ })
+
+ return keys, err
+}
diff --git a/vendor/github.com/99designs/keyring/prompt.go b/vendor/github.com/99designs/keyring/prompt.go
new file mode 100644
index 0000000..59ad81c
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/prompt.go
@@ -0,0 +1,27 @@
+package keyring
+
+import (
+ "fmt"
+ "os"
+
+ "golang.org/x/term"
+)
+
+// PromptFunc is a function used to prompt the user for a password.
+type PromptFunc func(string) (string, error)
+
+func TerminalPrompt(prompt string) (string, error) {
+ fmt.Printf("%s: ", prompt)
+ b, err := term.ReadPassword(int(os.Stdin.Fd()))
+ if err != nil {
+ return "", err
+ }
+ fmt.Println()
+ return string(b), nil
+}
+
+func FixedStringPrompt(value string) PromptFunc {
+ return func(_ string) (string, error) {
+ return value, nil
+ }
+}
diff --git a/vendor/github.com/99designs/keyring/secretservice.go b/vendor/github.com/99designs/keyring/secretservice.go
new file mode 100644
index 0000000..2a1f9d9
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/secretservice.go
@@ -0,0 +1,293 @@
+//go:build linux
+// +build linux
+
+package keyring
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+
+ "strings"
+
+ "github.com/godbus/dbus"
+ "github.com/gsterjov/go-libsecret"
+)
+
+func init() {
+ // silently fail if dbus isn't available
+ _, err := dbus.SessionBus()
+ if err != nil {
+ return
+ }
+
+ supportedBackends[SecretServiceBackend] = opener(func(cfg Config) (Keyring, error) {
+ if cfg.ServiceName == "" {
+ cfg.ServiceName = "secret-service"
+ }
+ if cfg.LibSecretCollectionName == "" {
+ cfg.LibSecretCollectionName = cfg.ServiceName
+ }
+
+ service, err := libsecret.NewService()
+ if err != nil {
+ return &secretsKeyring{}, err
+ }
+
+ ring := &secretsKeyring{
+ name: cfg.LibSecretCollectionName,
+ service: service,
+ }
+
+ return ring, ring.openSecrets()
+ })
+}
+
+type secretsKeyring struct {
+ name string
+ service *libsecret.Service
+ collection *libsecret.Collection
+ session *libsecret.Session
+}
+
+var errCollectionNotFound = errors.New("The collection does not exist. Please add a key first")
+
+func decodeKeyringString(src string) string {
+ var dst strings.Builder
+ for i := 0; i < len(src); i++ {
+ if src[i] != '_' {
+ dst.WriteString(string(src[i]))
+ } else {
+ if i+3 > len(src) {
+ return src
+ }
+ hexstring := src[i+1 : i+3]
+ decoded, err := hex.DecodeString(hexstring)
+ if err != nil {
+ return src
+ }
+ dst.Write(decoded)
+ i += 2
+ }
+ }
+ return dst.String()
+}
+
+func (k *secretsKeyring) openSecrets() error {
+ session, err := k.service.Open()
+ if err != nil {
+ return err
+ }
+ k.session = session
+
+ // get the collection if it already exists
+ collections, err := k.service.Collections()
+ if err != nil {
+ return err
+ }
+
+ path := libsecret.DBusPath + "/collection/" + k.name
+
+ for _, collection := range collections {
+ if decodeKeyringString(string(collection.Path())) == path {
+ c := collection // fix variable into the local variable to ensure it's referenced correctly, see https://github.com/kyoh86/exportloopref
+ k.collection = &c
+ return nil
+ }
+ }
+
+ return nil
+}
+
+func (k *secretsKeyring) openCollection() error {
+ if err := k.openSecrets(); err != nil {
+ return err
+ }
+
+ if k.collection == nil {
+ return errCollectionNotFound
+ // return &secretsError{fmt.Sprintf(
+ // "The collection %q does not exist. Please add a key first",
+ // k.name,
+ // )}
+ }
+
+ return nil
+}
+
+func (k *secretsKeyring) Get(key string) (Item, error) {
+ if err := k.openCollection(); err != nil {
+ if err == errCollectionNotFound {
+ return Item{}, ErrKeyNotFound
+ }
+ return Item{}, err
+ }
+
+ items, err := k.collection.SearchItems(key)
+ if err != nil {
+ return Item{}, err
+ }
+
+ if len(items) == 0 {
+ return Item{}, ErrKeyNotFound
+ }
+
+ // use the first item whenever there are multiples
+ // with the same profile name
+ item := items[0]
+
+ locked, err := item.Locked()
+ if err != nil {
+ return Item{}, err
+ }
+
+ if locked {
+ if err := k.service.Unlock(item); err != nil {
+ return Item{}, err
+ }
+ }
+
+ secret, err := item.GetSecret(k.session)
+ if err != nil {
+ return Item{}, err
+ }
+
+ // pack the secret into the item
+ var ret Item
+ if err = json.Unmarshal(secret.Value, &ret); err != nil {
+ return Item{}, err
+ }
+
+ return ret, err
+}
+
+// GetMetadata for libsecret returns an error indicating that it's unsupported
+// for this backend.
+//
+// libsecret actually implements a metadata system which we could use, "Secret
+// Attributes"; I found no indication in documentation of anything like an
+// automatically maintained last-modification timestamp, so to use this we'd
+// need to have a SetMetadata API too. Which we're not yet doing, but feel
+// free to contribute patches.
+func (k *secretsKeyring) GetMetadata(key string) (Metadata, error) {
+ return Metadata{}, ErrMetadataNeedsCredentials
+}
+
+func (k *secretsKeyring) Set(item Item) error {
+ err := k.openSecrets()
+ if err != nil {
+ return err
+ }
+
+ // create the collection if it doesn't already exist
+ if k.collection == nil {
+ collection, err := k.service.CreateCollection(k.name)
+ if err != nil {
+ return err
+ }
+
+ k.collection = collection
+ }
+
+ if err := k.ensureCollectionUnlocked(); err != nil {
+ return err
+ }
+
+ // create the new item
+ data, err := json.Marshal(item)
+ if err != nil {
+ return err
+ }
+
+ secret := libsecret.NewSecret(k.session, []byte{}, data, "application/json")
+
+ if _, err := k.collection.CreateItem(item.Key, secret, true); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (k *secretsKeyring) Remove(key string) error {
+ if err := k.openCollection(); err != nil {
+ if err == errCollectionNotFound {
+ return ErrKeyNotFound
+ }
+ return err
+ }
+
+ items, err := k.collection.SearchItems(key)
+ if err != nil {
+ return err
+ }
+
+ // nothing to delete
+ if len(items) == 0 {
+ return nil
+ }
+
+ // we dont want to delete more than one anyway
+ // so just get the first item found
+ item := items[0]
+
+ locked, err := item.Locked()
+ if err != nil {
+ return err
+ }
+
+ if locked {
+ if err := k.service.Unlock(item); err != nil {
+ return err
+ }
+ }
+
+ if err := item.Delete(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (k *secretsKeyring) Keys() ([]string, error) {
+ if err := k.openCollection(); err != nil {
+ if err == errCollectionNotFound {
+ return []string{}, nil
+ }
+ return nil, err
+ }
+ if err := k.ensureCollectionUnlocked(); err != nil {
+ return nil, err
+ }
+ items, err := k.collection.Items()
+ if err != nil {
+ return nil, err
+ }
+ keys := []string{}
+ for _, item := range items {
+ label, err := item.Label() // FIXME: err is being silently ignored
+ if err == nil {
+ keys = append(keys, label)
+ }
+ }
+ return keys, nil
+}
+
+// deleteCollection deletes the keyring's collection if it exists. This is mainly to support testing.
+func (k *secretsKeyring) deleteCollection() error {
+ if err := k.openCollection(); err != nil {
+ return err
+ }
+ return k.collection.Delete()
+}
+
+// unlock the collection if it's locked
+func (k *secretsKeyring) ensureCollectionUnlocked() error {
+ locked, err := k.collection.Locked()
+ if err != nil {
+ return err
+ }
+ if !locked {
+ return nil
+ }
+ return k.service.Unlock(k.collection)
+}
diff --git a/vendor/github.com/99designs/keyring/tilde.go b/vendor/github.com/99designs/keyring/tilde.go
new file mode 100644
index 0000000..c1847a6
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/tilde.go
@@ -0,0 +1,22 @@
+package keyring
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+var tildePrefix = string([]rune{'~', filepath.Separator})
+
+// ExpandTilde will expand tilde (~/ or ~\ depending on OS) for the user home directory.
+func ExpandTilde(dir string) (string, error) {
+ if strings.HasPrefix(dir, tildePrefix) {
+ homeDir, err := os.UserHomeDir()
+ if err != nil {
+ return "", err
+ }
+ dir = strings.Replace(dir, "~", homeDir, 1)
+ debugf("Expanded file dir to %s", dir)
+ }
+ return dir, nil
+}
diff --git a/vendor/github.com/99designs/keyring/wincred.go b/vendor/github.com/99designs/keyring/wincred.go
new file mode 100644
index 0000000..2ed43f2
--- /dev/null
+++ b/vendor/github.com/99designs/keyring/wincred.go
@@ -0,0 +1,98 @@
+//go:build windows
+// +build windows
+
+package keyring
+
+import (
+ "strings"
+ "syscall"
+
+ "github.com/danieljoos/wincred"
+)
+
+// ERROR_NOT_FOUND from https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--1000-1299-
+const elementNotFoundError = syscall.Errno(1168)
+
+type windowsKeyring struct {
+ name string
+ prefix string
+}
+
+func init() {
+ supportedBackends[WinCredBackend] = opener(func(cfg Config) (Keyring, error) {
+ name := cfg.ServiceName
+ if name == "" {
+ name = "default"
+ }
+
+ prefix := cfg.WinCredPrefix
+ if prefix == "" {
+ prefix = "keyring"
+ }
+
+ return &windowsKeyring{
+ name: name,
+ prefix: prefix,
+ }, nil
+ })
+}
+
+func (k *windowsKeyring) Get(key string) (Item, error) {
+ cred, err := wincred.GetGenericCredential(k.credentialName(key))
+ if err != nil {
+ if err == elementNotFoundError {
+ return Item{}, ErrKeyNotFound
+ }
+ return Item{}, err
+ }
+
+ item := Item{
+ Key: key,
+ Data: cred.CredentialBlob,
+ }
+
+ return item, nil
+}
+
+// GetMetadata for pass returns an error indicating that it's unsupported
+// for this backend.
+// TODO: This is a stub. Look into whether pass would support metadata in a usable way for keyring.
+func (k *windowsKeyring) GetMetadata(_ string) (Metadata, error) {
+ return Metadata{}, ErrMetadataNotSupported
+}
+
+func (k *windowsKeyring) Set(item Item) error {
+ cred := wincred.NewGenericCredential(k.credentialName(item.Key))
+ cred.CredentialBlob = item.Data
+ return cred.Write()
+}
+
+func (k *windowsKeyring) Remove(key string) error {
+ cred, err := wincred.GetGenericCredential(k.credentialName(key))
+ if err != nil {
+ if err == elementNotFoundError {
+ return ErrKeyNotFound
+ }
+ return err
+ }
+ return cred.Delete()
+}
+
+func (k *windowsKeyring) Keys() ([]string, error) {
+ results := []string{}
+
+ if creds, err := wincred.List(); err == nil {
+ for _, cred := range creds {
+ prefix := k.credentialName("")
+ if strings.HasPrefix(cred.TargetName, prefix) {
+ results = append(results, strings.TrimPrefix(cred.TargetName, prefix))
+ }
+ }
+ }
+
+ return results, nil
+}
+
+func (k *windowsKeyring) credentialName(key string) string {
+ return k.prefix + ":" + k.name + ":" + key
+}