pax_global_header00006660000000000000000000000064143477714050014526gustar00rootroot0000000000000052 comment=929f178361c0be06c50f0a82598cbfa1a965f40f keyring-1.2.2/000077500000000000000000000000001434777140500132005ustar00rootroot00000000000000keyring-1.2.2/.gitattributes000066400000000000000000000000211434777140500160640ustar00rootroot00000000000000*.go text eol=lf keyring-1.2.2/.github/000077500000000000000000000000001434777140500145405ustar00rootroot00000000000000keyring-1.2.2/.github/workflows/000077500000000000000000000000001434777140500165755ustar00rootroot00000000000000keyring-1.2.2/.github/workflows/lint.yml000066400000000000000000000006711434777140500202720ustar00rootroot00000000000000name: golangci-lint on: push jobs: golangci: strategy: matrix: go-version: [1.19] os: [macos-latest, windows-latest, ubuntu-latest] name: lint runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: go-version: 1.19 - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: version: v1.50.1 keyring-1.2.2/.github/workflows/test.yml000066400000000000000000000010061434777140500202740ustar00rootroot00000000000000name: Continuous Integration on: push jobs: linux: runs-on: ubuntu-latest steps: - uses: actions/setup-go@v2 with: go-version: 1.19 - run: sudo apt-get install pass gnome-keyring dbus-x11 - uses: actions/checkout@v2 - run: go test -race ./... mac: runs-on: macos-latest steps: - uses: actions/setup-go@v2 with: go-version: 1.19 - run: brew install pass gnupg - uses: actions/checkout@v2 - run: go test -race ./... keyring-1.2.2/.gitignore000066400000000000000000000000111434777140500151600ustar00rootroot00000000000000.vagrant keyring-1.2.2/.golangci.yml000066400000000000000000000006711434777140500155700ustar00rootroot00000000000000linters: 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 keyring-1.2.2/LICENSE000066400000000000000000000020651434777140500142100ustar00rootroot00000000000000The 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. keyring-1.2.2/README.md000066400000000000000000000050311434777140500144560ustar00rootroot00000000000000Keyring ======= [![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 keyring-1.2.2/Vagrantfile000066400000000000000000000064071434777140500153740ustar00rootroot00000000000000Vagrant.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 keyring-1.2.2/array.go000066400000000000000000000024621434777140500146510ustar00rootroot00000000000000package 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 } keyring-1.2.2/array_test.go000066400000000000000000000010071434777140500157020ustar00rootroot00000000000000package keyring import "testing" func TestArrayKeyringSetWhenEmpty(t *testing.T) { k := &ArrayKeyring{} item := Item{Key: "llamas", Data: []byte("llamas are great")} if err := k.Set(item); err != nil { t.Fatal(err) } foundItem, err := k.Get("llamas") if err != nil { t.Fatal(err) } if string(foundItem.Data) != "llamas are great" { t.Fatalf("Value stored was not the value retrieved: %q", foundItem.Data) } if foundItem.Key != "llamas" { t.Fatalf("Key wasn't persisted: %q", foundItem.Key) } } keyring-1.2.2/bin/000077500000000000000000000000001434777140500137505ustar00rootroot00000000000000keyring-1.2.2/bin/go-test000077500000000000000000000002041434777140500152540ustar00rootroot00000000000000#!/bin/bash echo "Local ($(uname)):" go test . echo echo "Linux:" ./bin/go-test-linux echo echo "Windows:" ./bin/go-test-windows keyring-1.2.2/bin/go-test-linux000077500000000000000000000000731434777140500164150ustar00rootroot00000000000000#!/bin/bash vagrant ssh linux -c 'cd /keyring; go test .' keyring-1.2.2/bin/go-test-windows000077500000000000000000000001021434777140500167410ustar00rootroot00000000000000#!/bin/bash vagrant winrm windows -e -c 'cd /keyring; go test .' keyring-1.2.2/cmd/000077500000000000000000000000001434777140500137435ustar00rootroot00000000000000keyring-1.2.2/cmd/keyring/000077500000000000000000000000001434777140500154135ustar00rootroot00000000000000keyring-1.2.2/cmd/keyring/main.go000066400000000000000000000046131434777140500166720ustar00rootroot00000000000000package main import ( "flag" "fmt" "log" "os" "github.com/99designs/keyring" ) func main() { serviceName := flag.String("service", "example", "The keyring service to use") keyName := flag.String("key", "example", "The key to use") backend := flag.String("backend", "", "A specific backend to use") debug := flag.Bool("debug", false, "Whether to enable debugging in keyring") listBackends := flag.Bool("list-backends", false, "Whether to list backends") // actions to take actionListKeys := flag.Bool("list-keys", false, "Whether to list keys") actionSetValue := flag.String("set", "", "The value to set") // keychain keychainName := flag.String("keychain", "login", "The keychain to search") flag.Parse() // Handle -list-backends if *listBackends { for _, b := range keyring.AvailableBackends() { fmt.Printf("%s\n", b) } os.Exit(0) } // Log to stderr log.SetOutput(os.Stderr) keyring.Debug = *debug var allowedBackends []keyring.BackendType if *backend != "" { if !hasBackend(*backend) { log.Fatalf("Backend %q isn't available. Use -list-backends to see what is.", *backend) } allowedBackends = append(allowedBackends, keyring.BackendType(*backend)) } else { allowedBackends = keyring.AvailableBackends() } ring, err := keyring.Open(keyring.Config{ ServiceName: *serviceName, AllowedBackends: allowedBackends, KeychainName: *keychainName, }) if err != nil { log.Fatal(err) } switch { case *actionListKeys: if *debug { log.Printf("Listing keys in service %q in backend %q", *serviceName, allowedBackends[0]) } keys, err := ring.Keys() if err != nil { log.Fatalf("Failed to list keys: %#v", err) } for _, key := range keys { fmt.Printf("%s\n", key) } case *actionSetValue != "": if *debug { log.Printf("Setting key %q in service %q in backend %q", *keyName, *serviceName, allowedBackends[0]) } err := ring.Set(keyring.Item{ Key: *keyName, Data: []byte(*actionSetValue), }) if err != nil { log.Fatal(err) } default: if *debug { log.Printf("Getting key %q in service %q in backend %q", *keyName, *serviceName, allowedBackends[0]) } i, err := ring.Get(*keyName) if err != nil { log.Fatal(err) } fmt.Printf("%s", i.Data) } } func hasBackend(key string) bool { for _, b := range keyring.AvailableBackends() { if keyring.BackendType(key) == b { return true } } return false } keyring-1.2.2/config.go000066400000000000000000000036421434777140500150010ustar00rootroot00000000000000package 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 } keyring-1.2.2/docker-compose.yml000066400000000000000000000002211434777140500166300ustar00rootroot00000000000000version: "3.9" services: keyring: image: golang:1.19 volumes: - .:/usr/local/src/keyring working_dir: /usr/local/src/keyring keyring-1.2.2/file.go000066400000000000000000000070211434777140500144460ustar00rootroot00000000000000package 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 } keyring-1.2.2/file_test.go000066400000000000000000000024061434777140500155070ustar00rootroot00000000000000package keyring import ( "os" "testing" ) func TestFileKeyringSetWhenEmpty(t *testing.T) { k := &fileKeyring{ dir: os.TempDir(), passwordFunc: FixedStringPrompt("no more secrets"), } item := Item{Key: "llamas", Data: []byte("llamas are great")} if err := k.Set(item); err != nil { t.Fatal(err) } foundItem, err := k.Get("llamas") if err != nil { t.Fatal(err) } if string(foundItem.Data) != "llamas are great" { t.Fatalf("Value stored was not the value retrieved: %q", foundItem.Data) } if foundItem.Key != "llamas" { t.Fatalf("Key wasn't persisted: %q", foundItem.Key) } } func TestFileKeyringGetWithSlashes(t *testing.T) { k := &fileKeyring{ dir: os.TempDir(), passwordFunc: FixedStringPrompt("no more secrets"), } item := Item{Key: "https://aws-sso-portal.awsapps.com/start", Data: []byte("https://aws-sso-portal.awsapps.com/start")} if err := k.Set(item); err != nil { t.Fatal(err) } if err := k.Remove(item.Key); err != nil { t.Fatal(err) } } func TestFilenameWithBadChars(t *testing.T) { a := `abc/.././123` e := filenameEscape(a) if e != `abc%2F..%2F.%2F123` { t.Fatalf("Unexpected result from filenameEscape: %s", e) } b := filenameUnescape(e) if b != a { t.Fatal("Unexpected filenameEscape") } } keyring-1.2.2/go.mod000066400000000000000000000012431434777140500143060ustar00rootroot00000000000000module github.com/99designs/keyring go 1.19 require ( github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 github.com/danieljoos/wincred v1.1.2 github.com/dvsekhvalnov/jose2go v1.5.0 github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c github.com/mtibben/percent v0.2.1 github.com/stretchr/testify v1.7.0 golang.org/x/sys v0.3.0 golang.org/x/term v0.3.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.3.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) keyring-1.2.2/go.sum000066400000000000000000000071441434777140500143410ustar00rootroot00000000000000github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= keyring-1.2.2/keychain.go000066400000000000000000000165251434777140500153330ustar00rootroot00000000000000//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) } keyring-1.2.2/keychain_test.go000066400000000000000000000126311434777140500163640ustar00rootroot00000000000000//go:build darwin // +build darwin package keyring import ( "fmt" "os" "path/filepath" "reflect" "testing" "time" ) func TestOSXKeychainKeyringSet(t *testing.T) { path := tempPath() defer deleteKeychain(t, path) k := &keychain{ path: path, passwordFunc: FixedStringPrompt("test password"), service: "test", isTrusted: true, } item := Item{ Key: "llamas", Label: "Arbitrary label", Description: "A freetext description", Data: []byte("llamas are great"), } if err := k.Set(item); err != nil { t.Fatal(err) } v, err := k.Get("llamas") if err != nil { t.Fatal(err) } if string(v.Data) != string(item.Data) { t.Fatalf("Data stored was not the data retrieved: %q vs %q", v.Data, item.Data) } if v.Key != item.Key { t.Fatalf("Key stored was not the data retrieved: %q vs %q", v.Key, item.Key) } if v.Description != item.Description { t.Fatalf("Description stored was not the data retrieved: %q vs %q", v.Description, item.Description) } } func TestOSXKeychainKeyringOverwrite(t *testing.T) { path := tempPath() defer deleteKeychain(t, path) k := &keychain{ path: path, passwordFunc: FixedStringPrompt("test password"), service: "test", isTrusted: true, } item1 := Item{ Key: "llamas", Label: "Arbitrary label", Description: "A freetext description", Data: []byte("llamas are ok"), } if err := k.Set(item1); err != nil { t.Fatal(err) } v1, err := k.Get("llamas") if err != nil { t.Fatal(err) } if string(v1.Data) != string(item1.Data) { t.Fatalf("Data stored was not the data retrieved: %q vs %q", v1.Data, item1.Data) } item2 := Item{ Key: "llamas", Label: "Arbitrary label", Description: "A freetext description", Data: []byte("llamas are great"), } if err := k.Set(item2); err != nil { t.Fatal(err) } v2, err := k.Get("llamas") if err != nil { t.Fatal(err) } if string(v2.Data) != string(item2.Data) { t.Fatalf("Data stored was not the data retrieved: %q vs %q", v2.Data, item2.Data) } } func TestOSXKeychainKeyringListKeysWhenEmpty(t *testing.T) { path := tempPath() defer deleteKeychain(t, path) k := &keychain{ path: path, service: "test", passwordFunc: FixedStringPrompt("test password"), isTrusted: true, } keys, err := k.Keys() if err != nil { t.Fatal(err) } if len(keys) != 0 { t.Fatalf("Expected 0 keys, got %d", len(keys)) } } func TestOSXKeychainKeyringListKeysWhenNotEmpty(t *testing.T) { path := tempPath() defer deleteKeychain(t, path) k := &keychain{ path: path, service: "test", passwordFunc: FixedStringPrompt("test password"), isTrusted: true, } keys := []string{"key1", "key2", "key3"} for _, key := range keys { item := Item{ Key: key, Data: []byte("llamas are great"), } if err := k.Set(item); err != nil { t.Fatal(err) } } keys2, err := k.Keys() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(keys, keys2) { t.Fatalf("Retrieved keys weren't the same: %q vs %q", keys, keys2) } } func deleteKeychain(t *testing.T, path string) { t.Helper() if _, err := os.Stat(path); os.IsExist(err) { _ = os.Remove(path) } // Sierra introduced a -db suffix dbPath := path + "-db" if _, err := os.Stat(dbPath); os.IsExist(err) { _ = os.Remove(dbPath) } } func TestOSXKeychainGetKeyWhenEmpty(t *testing.T) { path := tempPath() defer deleteKeychain(t, path) k := &keychain{ path: path, passwordFunc: FixedStringPrompt("test password"), service: "test", isTrusted: true, } _, err := k.Get("no-such-key") if err != ErrKeyNotFound { t.Fatal("expected ErrKeyNotFound") } } func TestOSXKeychainGetKeyWhenNotEmpty(t *testing.T) { path := tempPath() defer deleteKeychain(t, path) k := &keychain{ path: path, passwordFunc: FixedStringPrompt("test password"), service: "test", isTrusted: true, } item := Item{ Key: "llamas", Label: "Arbitrary label", Description: "A freetext description", Data: []byte("llamas are ok"), } if err := k.Set(item); err != nil { t.Fatal(err) } v1, err := k.Get("llamas") if err != nil { t.Fatal(err) } if string(v1.Data) != string(item.Data) { t.Fatalf("Data stored was not the data retrieved: %q vs %q", v1.Data, item.Data) } } func TestOSXKeychainRemoveKeyWhenEmpty(t *testing.T) { path := tempPath() defer deleteKeychain(t, path) k := &keychain{ path: path, passwordFunc: FixedStringPrompt("test password"), service: "test", isTrusted: true, } err := k.Remove("no-such-key") if err != ErrKeyNotFound { t.Fatalf("expected ErrKeyNotFound, got: %v", err) } } func TestOSXKeychainRemoveKeyWhenNotEmpty(t *testing.T) { path := tempPath() defer deleteKeychain(t, path) k := &keychain{ path: path, passwordFunc: FixedStringPrompt("test password"), service: "test", isTrusted: true, } item := Item{ Key: "llamas", Label: "Arbitrary label", Description: "A freetext description", Data: []byte("llamas are ok"), } if err := k.Set(item); err != nil { t.Fatal(err) } _, err := k.Get("llamas") if err != nil { t.Fatal(err) } err = k.Remove("llamas") if err != nil { t.Fatal(err) } } func tempPath() string { // TODO make filename configurable return filepath.Join(os.TempDir(), fmt.Sprintf("keyring-test-%d.keychain", time.Now().UnixNano())) } keyring-1.2.2/keyctl.go000066400000000000000000000214571434777140500150330ustar00rootroot00000000000000//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 } keyring-1.2.2/keyctl_test.go000066400000000000000000000156531434777140500160730ustar00rootroot00000000000000//go:build linux // +build linux package keyring_test import ( "errors" "math/rand" "syscall" "testing" "time" "github.com/99designs/keyring" "golang.org/x/sys/unix" "github.com/stretchr/testify/require" ) var ringname = getRandomKeyringName(16) const ringparent = "thread" func getRandomKeyringName(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" rand.Seed(time.Now().UnixNano()) buf := make([]byte, length) for i := range buf { buf[i] = charset[rand.Intn(len(charset))] } return "keyctl_test_" + string(buf) } func doesNamedKeyringExist() (bool, error) { ringparentID, err := keyring.GetKeyringIDForScope(ringparent) if err != nil { return false, nil //nolint:nilerr } _, err = unix.KeyctlSearch(int(ringparentID), "keyring", ringname, 0) if errors.Is(err, syscall.ENOKEY) { return false, nil } return err == nil, err } func cleanupNamedKeyring() { ringparentID, err := keyring.GetKeyringIDForScope(ringparent) if err != nil { return } named, err := unix.KeyctlSearch(int(ringparentID), "keyring", ringname, 0) if err != nil { return } _, _, _ = syscall.Syscall(syscall.SYS_KEYCTL, uintptr(unix.KEYCTL_UNLINK), uintptr(named), uintptr(ringparentID)) } func TestKeyCtlIsAvailable(t *testing.T) { backends := keyring.AvailableBackends() require.Containsf(t, backends, keyring.KeyCtlBackend, "keyctl backends not among %v", backends) } func TestKeyCtlOpenFailWrongScope(t *testing.T) { failingScopes := []string{"", "group", "invalid"} for _, scope := range failingScopes { _, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.KeyCtlBackend}, KeyCtlScope: scope, }) require.Errorf(t, err, "scope %q should fail", scope) } } func TestKeyCtlOpen(t *testing.T) { scopes := []string{"user", "session", "process", "thread"} for _, scope := range scopes { _, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.KeyCtlBackend}, KeyCtlScope: scope, }) require.NoError(t, err) } } func TestKeyCtlOpenNamed(t *testing.T) { exists, err := doesNamedKeyringExist() require.Falsef(t, exists, "ring %q already exists in scope %q", ringname, ringparent) require.NoErrorf(t, err, "checking for ring %q in scope %q failed: %v", ringname, ringparent, err) t.Cleanup(cleanupNamedKeyring) _, err = keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.KeyCtlBackend}, KeyCtlScope: ringparent, ServiceName: ringname, }) require.NoError(t, err) } func TestKeyCtlSet(t *testing.T) { kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.KeyCtlBackend}, KeyCtlScope: "user", KeyCtlPerm: 0x3f3f0000, // "alswrvalswrv------------" }) require.NoError(t, err) item1 := keyring.Item{ Key: "test", Data: []byte("loose lips sink ships"), } require.NoError(t, kr.Set(item1)) item2, err := kr.Get("test") require.NoError(t, err) require.Equal(t, item1, item2) require.NoError(t, kr.Remove("test")) _, err = kr.Get("test") require.Error(t, err) require.ErrorIs(t, err, keyring.ErrKeyNotFound) } func TestKeyCtlSetNamed(t *testing.T) { exists, err := doesNamedKeyringExist() require.Falsef(t, exists, "ring %q already exists in scope %q", ringname, ringparent) require.NoErrorf(t, err, "checking for ring %q in scope %q failed: %v", ringname, ringparent, err) t.Cleanup(cleanupNamedKeyring) kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.KeyCtlBackend}, KeyCtlScope: ringparent, ServiceName: ringname, KeyCtlPerm: 0x3f3f0000, // "alswrvalswrv------------" }) require.NoError(t, err) item1 := keyring.Item{ Key: "test", Data: []byte("loose lips sink ships"), } require.NoError(t, kr.Set(item1)) item2, err := kr.Get("test") require.NoError(t, err) require.Equal(t, item1, item2) require.NoError(t, kr.Remove("test")) _, err = kr.Get("test") require.Error(t, err) require.ErrorIs(t, err, keyring.ErrKeyNotFound) } func TestKeyCtlList(t *testing.T) { exists, err := doesNamedKeyringExist() require.Falsef(t, exists, "ring %q already exists in scope %q", ringname, ringparent) require.NoErrorf(t, err, "checking for ring %q in scope %q failed: %v", ringname, ringparent, err) t.Cleanup(cleanupNamedKeyring) kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.KeyCtlBackend}, KeyCtlScope: ringparent, ServiceName: ringname, KeyCtlPerm: 0x3f3f0000, // "alswrvalswrv------------" }) require.NoError(t, err) item1 := keyring.Item{ Key: "test", Data: []byte("loose lips sink ships"), } require.NoError(t, kr.Set(item1)) item2 := keyring.Item{ Key: "foobar", Data: []byte("don't foo the bar"), } require.NoError(t, kr.Set(item2)) keys, err := kr.Keys() require.NoError(t, err) expected := []string{"test", "foobar"} require.ElementsMatch(t, keys, expected) require.NoError(t, kr.Remove("test")) require.NoError(t, kr.Remove("foobar")) } func TestKeyCtlGetNonExisting(t *testing.T) { exists, err := doesNamedKeyringExist() require.Falsef(t, exists, "ring %q already exists in scope %q", ringname, ringparent) require.NoErrorf(t, err, "checking for ring %q in scope %q failed: %v", ringname, ringparent, err) t.Cleanup(cleanupNamedKeyring) kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.KeyCtlBackend}, KeyCtlScope: ringparent, ServiceName: ringname, KeyCtlPerm: 0x3f3f0000, // "alswrvalswrv------------" }) require.NoError(t, err) _, err = kr.Get("llamas") require.Error(t, err) require.ErrorIs(t, err, keyring.ErrKeyNotFound) } func TestKeyCtlRemoveNonExisting(t *testing.T) { exists, err := doesNamedKeyringExist() require.Falsef(t, exists, "ring %q already exists in scope %q", ringname, ringparent) require.NoErrorf(t, err, "checking for ring %q in scope %q failed: %v", ringname, ringparent, err) t.Cleanup(cleanupNamedKeyring) kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.KeyCtlBackend}, KeyCtlScope: ringparent, ServiceName: ringname, KeyCtlPerm: 0x3f3f0000, // "alswrvalswrv------------" }) require.NoError(t, err) err = kr.Remove("no-such-key") require.Error(t, err) require.ErrorIs(t, err, keyring.ErrKeyNotFound) } func TestKeyCtlListEmptyKeyring(t *testing.T) { exists, err := doesNamedKeyringExist() require.Falsef(t, exists, "ring %q already exists in scope %q", ringname, ringparent) require.NoErrorf(t, err, "checking for ring %q in scope %q failed: %v", ringname, ringparent, err) t.Cleanup(cleanupNamedKeyring) kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.KeyCtlBackend}, KeyCtlScope: ringparent, ServiceName: ringname, KeyCtlPerm: 0x3f3f0000, // "alswrvalswrv------------" }) require.NoError(t, err) keys, err := kr.Keys() require.NoError(t, err) require.Len(t, keys, 0) } keyring-1.2.2/keyring.go000066400000000000000000000073671434777140500152140ustar00rootroot00000000000000// 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...) } } keyring-1.2.2/keyring_test.go000066400000000000000000000005651434777140500162440ustar00rootroot00000000000000package keyring_test import ( "log" "github.com/99designs/keyring" ) func ExampleOpen() { // Use the best keyring implementation for your operating system kr, err := keyring.Open(keyring.Config{ ServiceName: "my-service", }) if err != nil { log.Fatal(err) } v, err := kr.Get("llamas") if err != nil { log.Fatal(err) } log.Printf("llamas was %v", v) } keyring-1.2.2/kwallet.go000066400000000000000000000121351434777140500151740ustar00rootroot00000000000000//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 } keyring-1.2.2/pass.go000066400000000000000000000060071434777140500145000ustar00rootroot00000000000000//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 } keyring-1.2.2/pass_test.go000066400000000000000000000116551434777140500155440ustar00rootroot00000000000000//go:build !windows // +build !windows package keyring import ( "bytes" "fmt" "os" "os/exec" "path/filepath" "reflect" "testing" ) func runCmd(t *testing.T, cmds ...string) { t.Helper() cmd := exec.Command(cmds[0], cmds[1:]...) out, err := cmd.CombinedOutput() if err != nil { fmt.Println(cmd) fmt.Println(string(out)) t.Fatal(err) } } func setup(t *testing.T) (*passKeyring, func(t *testing.T)) { t.Helper() pwd, err := os.Getwd() if err != nil { t.Fatal(err) } // the default temp directory can't be used because gpg-agent complains with "socket name too long" tmpdir, err := os.MkdirTemp("/tmp", "keyring-pass-test-*") if err != nil { t.Fatal(err) } // Initialise a blank GPG homedir; import & trust the test key gnupghome := filepath.Join(tmpdir, ".gnupg") err = os.Mkdir(gnupghome, os.FileMode(int(0700))) if err != nil { t.Fatal(err) } os.Setenv("GNUPGHOME", gnupghome) os.Unsetenv("GPG_AGENT_INFO") os.Unsetenv("GPG_TTY") runCmd(t, "gpg", "--import", filepath.Join(pwd, "testdata", "test-gpg.key")) runCmd(t, "gpg", "--import-ownertrust", filepath.Join(pwd, "testdata", "test-ownertrust-gpg.txt")) passdir := filepath.Join(tmpdir, ".password-store") k := &passKeyring{ dir: passdir, passcmd: "pass", prefix: "keyring", } cmd := k.pass("init", "test@example.com") cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { t.Fatal(err) } return k, func(t *testing.T) { t.Helper() os.RemoveAll(tmpdir) } } func TestPassKeyringSetWhenEmpty(t *testing.T) { k, teardown := setup(t) defer teardown(t) item := Item{Key: "llamas", Data: []byte("llamas are great")} if err := k.Set(item); err != nil { t.Fatal(err) } foundItem, err := k.Get("llamas") if err != nil { t.Fatal(err) } if string(foundItem.Data) != "llamas are great" { t.Fatalf("Value stored was not the value retrieved: %q", foundItem.Data) } if foundItem.Key != "llamas" { t.Fatalf("Key wasn't persisted: %q", foundItem.Key) } } func TestPassKeyringKeysWhenEmpty(t *testing.T) { k, teardown := setup(t) defer teardown(t) keys, err := k.Keys() if err != nil { t.Fatal(err) } if len(keys) != 0 { t.Fatalf("Expected 0 keys, got %d", len(keys)) } } func TestPassKeyringKeysWhenNotEmpty(t *testing.T) { k, teardown := setup(t) defer teardown(t) items := []Item{ {Key: "llamas", Data: []byte("llamas are great")}, {Key: "alpacas", Data: []byte("alpacas are better")}, {Key: "africa/elephants", Data: []byte("who doesn't like elephants")}, } for _, item := range items { if err := k.Set(item); err != nil { t.Fatal(err) } } keys, err := k.Keys() if err != nil { t.Fatal(err) } if len(keys) != len(items) { t.Fatalf("Expected %d keys, got %d", len(items), len(keys)) } expectedKeys := []string{ "africa/elephants", "alpacas", "llamas", } if !reflect.DeepEqual(keys, expectedKeys) { t.Fatalf("Expected keys %v, got %v", expectedKeys, keys) } } func TestPassKeyringRemoveWhenEmpty(t *testing.T) { k, teardown := setup(t) defer teardown(t) err := k.Remove("no-such-key") if err != ErrKeyNotFound { t.Fatalf("expected ErrKeyNotFound, got: %v", err) } } func TestPassKeyringRemoveWhenNotEmpty(t *testing.T) { k, teardown := setup(t) defer teardown(t) item := Item{Key: "llamas", Data: []byte("llamas are great")} if err := k.Set(item); err != nil { t.Fatal(err) } if err := k.Remove(item.Key); err != nil { t.Fatalf(err.Error()) } keys, err := k.Keys() if err != nil { t.Fatal(err) } if len(keys) != 0 { t.Fatalf("Expected 0 keys, got %d", len(keys)) } } func TestPassKeyringGetWhenEmpty(t *testing.T) { k, teardown := setup(t) defer teardown(t) _, err := k.Get("no-such-key") if err != ErrKeyNotFound { t.Fatalf("expected ErrKeyNotFound, got: %v", err) } } func TestPassKeyringGetWhenNotEmpty(t *testing.T) { k, teardown := setup(t) defer teardown(t) item := Item{Key: "llamas", Data: []byte("llamas are great")} if err := k.Set(item); err != nil { t.Fatal(err) } v1, err := k.Get(item.Key) if err != nil { t.Fatal(err) } if !bytes.Equal(v1.Data, item.Data) { t.Fatal("Expected item not returned") } } func TestPassKeyringKeysWithSymlink(t *testing.T) { k, teardown := setup(t) defer teardown(t) items := []Item{ {Key: "llamas", Data: []byte("llamas are great")}, {Key: "alpacas", Data: []byte("alpacas are better")}, {Key: "africa/elephants", Data: []byte("who doesn't like elephants")}, } for _, item := range items { if err := k.Set(item); err != nil { t.Fatal(err) } } s := filepath.Join(t.TempDir(), "newsymlink") err := os.Symlink(k.dir, s) if err != nil { t.Fatal(err) } k.dir = s keys, err := k.Keys() if err != nil { t.Fatal(err) } if len(keys) != len(items) { t.Fatalf("Expected %d keys, got %d", len(items), len(keys)) } expectedKeys := []string{ "africa/elephants", "alpacas", "llamas", } if !reflect.DeepEqual(keys, expectedKeys) { t.Fatalf("Expected keys %v, got %v", expectedKeys, keys) } } keyring-1.2.2/prompt.go000066400000000000000000000007661434777140500150610ustar00rootroot00000000000000package 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 } } keyring-1.2.2/secretservice.go000066400000000000000000000136651434777140500164100ustar00rootroot00000000000000//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) } keyring-1.2.2/secretservice_test.go000066400000000000000000000071421434777140500174400ustar00rootroot00000000000000//go:build linux // +build linux package keyring import ( "os" "sort" "testing" "github.com/gsterjov/go-libsecret" ) // NOTE: These tests are not runnable from a headless environment such as // Docker or a CI pipeline due to the DBus "prompt" interface being called // by the underlying go-libsecret when creating and unlocking a keychain. // // TODO: Investigate a way to automate the prompting. Some ideas: // // 1. I've looked extensively but have not found a headless CLI tool that // could be run in the background of eg: a docker container // 2. It might be possible to make a mock prompter that connects to DBus // and provides the Prompt interface using the go-libsecret library. func libSecretSetup(t *testing.T) (Keyring, func(t *testing.T)) { t.Helper() if os.Getenv("GITHUB_ACTIONS") != "" { t.Skip("Skipping testing in CI environment") } service, err := libsecret.NewService() if err != nil { t.Fatal(err) } kr := &secretsKeyring{ name: "keyring-test", service: service, } return kr, func(t *testing.T) { t.Helper() if err := kr.deleteCollection(); err != nil { t.Fatal(err) } } } func TestLibSecretKeysWhenEmpty(t *testing.T) { kr, _ := libSecretSetup(t) keys, err := kr.Keys() if err != nil { t.Fatal(err) } if len(keys) != 0 { t.Fatalf("Expected 0 keys, got %d", len(keys)) } } func TestLibSecretKeysWhenNotEmpty(t *testing.T) { kr, teardown := libSecretSetup(t) defer teardown(t) item := Item{Key: "llamas", Data: []byte("llamas are great")} item2 := Item{Key: "alpacas", Data: []byte("alpacas are better")} if err := kr.Set(item); err != nil { t.Fatal(err) } if err := kr.Set(item2); err != nil { t.Fatal(err) } keys, err := kr.Keys() if err != nil { t.Fatal(err) } if len(keys) != 2 { t.Fatalf("Expected 2 keys, got %d", len(keys)) } sort.Strings(keys) if keys[0] != "alpacas" { t.Fatalf("Expected alpacas") } if keys[1] != "llamas" { t.Fatalf("Expected llamas") } } func TestLibSecretGetWhenEmpty(t *testing.T) { kr, _ := libSecretSetup(t) _, err := kr.Get("llamas") if err != ErrKeyNotFound { t.Fatalf("Expected ErrKeyNotFound, got: %s", err) } } func TestLibSecretGetWhenNotEmpty(t *testing.T) { kr, teardown := libSecretSetup(t) defer teardown(t) item := Item{Key: "llamas", Data: []byte("llamas are great")} if err := kr.Set(item); err != nil { t.Fatal(err) } it, err := kr.Get(item.Key) if err != nil { t.Fatal(err) } if it.Key != item.Key { t.Fatal("Expected item not returned") } } func TestLibSecretRemoveWhenEmpty(t *testing.T) { kr, _ := libSecretSetup(t) err := kr.Remove("no-such-key") if err != ErrKeyNotFound { t.Fatalf("Expected ErrKeyNotFound, got: %s", err) } } func TestLibSecretRemoveWhenNotEmpty(t *testing.T) { kr, teardown := libSecretSetup(t) defer teardown(t) item := Item{Key: "llamas", Data: []byte("llamas are great")} if err := kr.Set(item); err != nil { t.Fatal(err) } if _, err := kr.Get("llamas"); err != nil { t.Fatal(err) } if err := kr.Remove("llamas"); err != nil { t.Fatal(err) } } func TestLibSpecialCharacters(t *testing.T) { decoded := decodeKeyringString("keyring_2dtest") if decoded != "keyring-test" { t.Fatal("incorrect decodeKeyringString") } decoded = decodeKeyringString("keyring_2d_2dtest") if decoded != "keyring--test" { t.Fatal("incorrect decodeKeyringString") } decoded = decodeKeyringString("keyring_2dtest_2d_2d") if decoded != "keyring-test--" { t.Fatal("incorrect decodeKeyringString") } decoded = decodeKeyringString("_2d_2dkeyring_2dtest_2d_2d") if decoded != "--keyring-test--" { t.Fatal("incorrect decodeKeyringString") } } keyring-1.2.2/testdata/000077500000000000000000000000001434777140500150115ustar00rootroot00000000000000keyring-1.2.2/testdata/test-gpg.key000066400000000000000000000116661434777140500172670ustar00rootroot00000000000000-----BEGIN PGP PRIVATE KEY BLOCK----- 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 UW2OceZnWdDZeMb48Nxp3C+z =48xf -----END PGP PRIVATE KEY BLOCK----- keyring-1.2.2/testdata/test-ownertrust-gpg.txt000066400000000000000000000002441434777140500215360ustar00rootroot00000000000000# List of assigned trustvalues, created Thu 15 Feb 2018 11:51:13 AEDT # (Use "gpg --import-ownertrust" to restore them) DA82DC74F44C4CCB23BF82D0216EDDD0042BFA30:6: keyring-1.2.2/tilde.go000066400000000000000000000007441434777140500146350ustar00rootroot00000000000000package 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 } keyring-1.2.2/tilde_test.go000066400000000000000000000015111434777140500156650ustar00rootroot00000000000000//go:build linux // +build linux package keyring import "testing" func TestExpandTilde(t *testing.T) { t.Setenv("HOME", "/home/testing") actual, err := ExpandTilde("~/one/two") if err != nil { t.Fatal(err) } expected := "/home/testing/one/two" if actual != expected { t.Fatalf("%s != %s", expected, actual) } } func TestExpandTildeWithoutSlash(t *testing.T) { t.Setenv("HOME", "/home/testing") actual, err := ExpandTilde("~one/two") if err != nil { t.Fatal(err) } expected := "~one/two" if actual != expected { t.Fatalf("%s != %s", expected, actual) } } func TestExpandTildeWithoutLeadingTilde(t *testing.T) { t.Setenv("HOME", "/home/testing") actual, err := ExpandTilde("one/two~") if err != nil { t.Fatal(err) } expected := "one/two~" if actual != expected { t.Fatalf("%s != %s", expected, actual) } } keyring-1.2.2/wincred.go000066400000000000000000000041561434777140500151700ustar00rootroot00000000000000//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 } keyring-1.2.2/wincred_test.go000066400000000000000000000044721434777140500162300ustar00rootroot00000000000000//go:build windows // +build windows package keyring_test import ( "reflect" "testing" "github.com/99designs/keyring" ) func TestSavingCredentialsWithWinCred(t *testing.T) { kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.WinCredBackend}, }) if err != nil { t.Fatal(err) } item1 := keyring.Item{ Key: "test", Data: []byte("loose lips sink ships"), } err = kr.Set(item1) if err != nil { t.Fatal(err) } item2, err := kr.Get("test") if err != nil { t.Fatal(err) } if !reflect.DeepEqual(item1, item2) { t.Fatalf("Expected %#v, got %#v", item1, item2) } err = kr.Remove("test") if err != nil { t.Fatal(err) } _, err = kr.Get("test") if err != keyring.ErrKeyNotFound { t.Fatalf("Expected %v, got %v", keyring.ErrKeyNotFound, err) } } func TestListingCredentialsWithWinCred(t *testing.T) { kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.WinCredBackend}, }) if err != nil { t.Fatal(err) } item1 := keyring.Item{ Key: "test", Data: []byte("loose lips sink ships"), } err = kr.Set(item1) if err != nil { t.Fatal(err) } keys, err := kr.Keys() if err != nil { t.Fatal(err) } if expected := []string{"test"}; !reflect.DeepEqual(keys, expected) { t.Fatalf("Unexpected keys, got %#v, expected %#v", keys, expected) } err = kr.Remove("test") if err != nil { t.Fatal(err) } } func TestWinCredGetWhenEmpty(t *testing.T) { kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.WinCredBackend}, }) if err != nil { t.Fatal(err) } _, err = kr.Get("llamas") if err != keyring.ErrKeyNotFound { t.Fatal("Expected ErrKeyNotFound") } } func TestWinCredRemoveWhenEmpty(t *testing.T) { kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.WinCredBackend}, }) if err != nil { t.Fatal(err) } err = kr.Remove("no-such-key") if err != keyring.ErrKeyNotFound { t.Fatal("Expected ErrKeyNotFound") } } func TestWinCredKeysWhenEmpty(t *testing.T) { kr, err := keyring.Open(keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.WinCredBackend}, }) if err != nil { t.Fatal(err) } keys, err := kr.Keys() if err != nil { t.Fatal(err) } if len(keys) != 0 { t.Fatalf("Expected 0 keys, got %d", len(keys)) } }