pax_global_header00006660000000000000000000000064145751356640014532gustar00rootroot0000000000000052 comment=987647a77244da26198ed51b2d8a29ccc11bceee go-keyring-0.2.4/000077500000000000000000000000001457513566400136105ustar00rootroot00000000000000go-keyring-0.2.4/.catwatch.yml000066400000000000000000000000221457513566400162010ustar00rootroot00000000000000title: go-keyring go-keyring-0.2.4/.github/000077500000000000000000000000001457513566400151505ustar00rootroot00000000000000go-keyring-0.2.4/.github/workflows/000077500000000000000000000000001457513566400172055ustar00rootroot00000000000000go-keyring-0.2.4/.github/workflows/go.yml000066400000000000000000000044611457513566400203420ustar00rootroot00000000000000name: Go on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build-linux: name: Build (ubuntu-latest) runs-on: ubuntu-latest container: image: ubuntu:latest options: --privileged env: DEBIAN_FRONTEND: noninteractive steps: - name: Check out code into $GITHUB_WORKSPACE directory uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: ^1.19 id: go - name: Install Dependencies run: | apt-get update apt-get install -y gnome-keyring build-essential ca-certificates mkdir -p /github/home/.cache/ mkdir -p /github/home/.local/share/keyrings/ chmod 700 -R /github/home/.local/ - name: Get dependencies run: | go get -v -t -d ./... - name: Build run: | go build -v ./... - name: Test run: | echo 'somecredstorepass' | gnome-keyring-daemon --unlock go test -v ./... shell: dbus-run-session -- bash --noprofile --norc -eo pipefail {0} build-other: name: Build runs-on: ${{ matrix.os }} strategy: matrix: os: [macOS-latest, windows-latest] steps: - name: Check out code into $GITHUB_WORKSPACE directory uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: ^1.19 id: go - name: Get dependencies run: | go get -v -t -d ./... - name: Build run: | go build -v ./... - name: Test run: | go test -v ./... build-freebsd: name: Build (FreeBSD) runs-on: macos-12 steps: - uses: actions/checkout@v2 - name: Test in FreeBSD id: test uses: vmactions/freebsd-vm@v0.3.0 with: usesh: true prepare: pkg install -y go gnome-keyring run: | go version go build -v dbus-run-session -- sh -c "echo 'somecredstorepass' | gnome-keyring-daemon --unlock; go test -v ./..." # verify that we can build for freebsd with cgo disabled # This will disable the functionality as it depends on cgo (via # godbus) but should still be buildable to not break backwards # compatibility CGO_ENABLED=0 go build -v go-keyring-0.2.4/.gitignore000066400000000000000000000010721457513566400156000ustar00rootroot00000000000000# https://github.com/github/gitignore ######################### Go ################################################### # https://raw.githubusercontent.com/github/gitignore/master/Go.gitignore ################################################################################ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) vendor/ # Go workspace file go.work go-keyring-0.2.4/.zappr.yml000066400000000000000000000001721457513566400155450ustar00rootroot00000000000000approvals: groups: zalando: minimum: 2 from: orgs: - "zalando" X-Zalando-Team: teapot go-keyring-0.2.4/CONTRIBUTING.md000066400000000000000000000007601457513566400160440ustar00rootroot00000000000000# Contributing to Go keyring library Please open an issue for bugs and feature requests. We are open to Pull Requests, but we would like to discuss features with you. If you want to submit a PR, always use feature branches and let people discuss changes in pull requests. Pull requests should only be merged after all discussions have been concluded and at least 2 reviewers has given their **approval**. ## Guidelines - **every code change** should have a test - keep the current code style go-keyring-0.2.4/LICENSE000066400000000000000000000020651457513566400146200ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Zalando SE 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. go-keyring-0.2.4/MAINTAINERS000066400000000000000000000001401457513566400153000ustar00rootroot00000000000000Mikkel Oscar Lyderik Larsen Sandor Szücs go-keyring-0.2.4/README.md000066400000000000000000000076521457513566400151010ustar00rootroot00000000000000# Go Keyring library [![Go Report Card](https://goreportcard.com/badge/github.com/zalando/go-keyring)](https://goreportcard.com/report/github.com/zalando/go-keyring) [![GoDoc](https://godoc.org/github.com/zalando/go-keyring?status.svg)](https://godoc.org/github.com/zalando/go-keyring) `go-keyring` is an OS-agnostic library for *setting*, *getting* and *deleting* secrets from the system keyring. It supports **OS X**, **Linux/BSD (dbus)** and **Windows**. go-keyring was created after its authors searched for, but couldn't find, a better alternative. It aims to simplify using statically linked binaries, which is cumbersome when relying on C bindings (as other keyring libraries do). #### Potential Uses If you're working with an application that needs to store user credentials locally on the user's machine, go-keyring might come in handy. For instance, if you are writing a CLI for an API that requires a username and password, you can store this information in the keyring instead of having the user type it on every invocation. ## Dependencies #### OS X The OS X implementation depends on the `/usr/bin/security` binary for interfacing with the OS X keychain. It should be available by default. #### Linux and *BSD The Linux and *BSD implementation depends on the [Secret Service][SecretService] dbus interface, which is provided by [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring). It's expected that the default collection `login` exists in the keyring, because it's the default in most distros. If it doesn't exist, you can create it through the keyring frontend program [Seahorse](https://wiki.gnome.org/Apps/Seahorse): * Open `seahorse` * Go to **File > New > Password Keyring** * Click **Continue** * When asked for a name, use: **login** ## Example Usage How to *set* and *get* a secret from the keyring: ```go package main import ( "log" "github.com/zalando/go-keyring" ) func main() { service := "my-app" user := "anon" password := "secret" // set password err := keyring.Set(service, user, password) if err != nil { log.Fatal(err) } // get password secret, err := keyring.Get(service, user) if err != nil { log.Fatal(err) } log.Println(secret) } ``` ## Tests ### Running tests Running the tests is simple: ``` go test ``` Which OS you use *does* matter. If you're using **Linux** or **BSD**, it will test the implementation in `keyring_unix.go`. If running the tests on **OS X**, it will test the implementation in `keyring_darwin.go`. ### Mocking If you need to mock the keyring behavior for testing on systems without a keyring implementation you can call `MockInit()` which will replace the OS defined provider with an in-memory one. ```go package implementation import ( "testing" "github.com/zalando/go-keyring" ) func TestMockedSetGet(t *testing.T) { keyring.MockInit() err := keyring.Set("service", "user", "password") if err != nil { t.Fatal(err) } p, err := keyring.Get("service", "user") if err != nil { t.Fatal(err) } if p != "password" { t.Error("password was not the expected string") } } ``` ## Contributing/TODO We welcome contributions from the community; please use [CONTRIBUTING.md](CONTRIBUTING.md) as your guidelines for getting started. Here are some items that we'd love help with: - The code base - Better test coverage Please use GitHub issues as the starting point for contributions, new ideas and/or bug reports. ## Contact * E-Mail: team-teapot@zalando.de * Security issues: Please send an email to the [maintainers](MAINTAINERS), and we'll try to get back to you within two workdays. If you don't hear back, send an email to team-teapot@zalando.de and someone will respond within five days max. ## Contributors Thanks to: - [your name here] ## License See [LICENSE](LICENSE) file. [SecretService]: https://specifications.freedesktop.org/secret-service/latest/ go-keyring-0.2.4/SECURITY.md000066400000000000000000000011361457513566400154020ustar00rootroot00000000000000We acknowledge that every line of code that we write may potentially contain security issues. We are trying to deal with it responsibly and provide patches as quickly as possible. We host our bug bounty program on HackerOne, it is currently private, therefore if you would like to report a vulnerability and get rewarded for it, please ask to join our program by filling this form: https://corporate.zalando.com/en/services-and-contact#security-form You can also send your report via this form if you do not want to join our bug bounty program and just want to report a vulnerability or security issue. go-keyring-0.2.4/go.mod000066400000000000000000000003271457513566400147200ustar00rootroot00000000000000module github.com/zalando/go-keyring go 1.18 require ( github.com/alessio/shellescape v1.4.1 github.com/danieljoos/wincred v1.2.0 github.com/godbus/dbus/v5 v5.1.0 ) require golang.org/x/sys v0.8.0 // indirect go-keyring-0.2.4/go.sum000066400000000000000000000020661457513566400147470ustar00rootroot00000000000000github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= go-keyring-0.2.4/keyring.go000066400000000000000000000027151457513566400156140ustar00rootroot00000000000000package keyring import "errors" // provider set in the init function by the relevant os file e.g.: // keyring_unix.go var provider Keyring = fallbackServiceProvider{} var ( // ErrNotFound is the expected error if the secret isn't found in the // keyring. ErrNotFound = errors.New("secret not found in keyring") // ErrSetDataTooBig is returned if `Set` was called with too much data. // On MacOS: The combination of service, username & password should not exceed ~3000 bytes // On Windows: The service is limited to 32KiB while the password is limited to 2560 bytes // On Linux/Unix: There is no theoretical limit but performance suffers with big values (>100KiB) ErrSetDataTooBig = errors.New("data passed to Set was too big") ) // Keyring provides a simple set/get interface for a keyring service. type Keyring interface { // Set password in keyring for user. Set(service, user, password string) error // Get password from keyring given service and user name. Get(service, user string) (string, error) // Delete secret from keyring. Delete(service, user string) error } // Set password in keyring for user. func Set(service, user, password string) error { return provider.Set(service, user, password) } // Get password from keyring given service and user name. func Get(service, user string) (string, error) { return provider.Get(service, user) } // Delete secret from keyring. func Delete(service, user string) error { return provider.Delete(service, user) } go-keyring-0.2.4/keyring_darwin.go000066400000000000000000000063271457513566400171630ustar00rootroot00000000000000// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package keyring import ( "encoding/base64" "encoding/hex" "fmt" "io" "os/exec" "strings" "github.com/alessio/shellescape" ) const ( execPathKeychain = "/usr/bin/security" // encodingPrefix is a well-known prefix added to strings encoded by Set. encodingPrefix = "go-keyring-encoded:" base64EncodingPrefix = "go-keyring-base64:" ) type macOSXKeychain struct{} // func (*MacOSXKeychain) IsAvailable() bool { // return exec.Command(execPathKeychain).Run() != exec.ErrNotFound // } // Get password from macos keyring given service and user name. func (k macOSXKeychain) Get(service, username string) (string, error) { out, err := exec.Command( execPathKeychain, "find-generic-password", "-s", service, "-wa", username).CombinedOutput() if err != nil { if strings.Contains(string(out), "could not be found") { err = ErrNotFound } return "", err } trimStr := strings.TrimSpace(string(out[:])) // if the string has the well-known prefix, assume it's encoded if strings.HasPrefix(trimStr, encodingPrefix) { dec, err := hex.DecodeString(trimStr[len(encodingPrefix):]) return string(dec), err } else if strings.HasPrefix(trimStr, base64EncodingPrefix) { dec, err := base64.StdEncoding.DecodeString(trimStr[len(base64EncodingPrefix):]) return string(dec), err } return trimStr, nil } // Set stores a secret in the macos keyring given a service name and a user. func (k macOSXKeychain) Set(service, username, password string) error { // if the added secret has multiple lines or some non ascii, // osx will hex encode it on return. To avoid getting garbage, we // encode all passwords password = base64EncodingPrefix + base64.StdEncoding.EncodeToString([]byte(password)) cmd := exec.Command(execPathKeychain, "-i") stdIn, err := cmd.StdinPipe() if err != nil { return err } if err = cmd.Start(); err != nil { return err } command := fmt.Sprintf("add-generic-password -U -s %s -a %s -w %s\n", shellescape.Quote(service), shellescape.Quote(username), shellescape.Quote(password)) if len(command) > 4096 { return ErrSetDataTooBig } if _, err := io.WriteString(stdIn, command); err != nil { return err } if err = stdIn.Close(); err != nil { return err } err = cmd.Wait() return err } // Delete deletes a secret, identified by service & user, from the keyring. func (k macOSXKeychain) Delete(service, username string) error { out, err := exec.Command( execPathKeychain, "delete-generic-password", "-s", service, "-a", username).CombinedOutput() if strings.Contains(string(out), "could not be found") { err = ErrNotFound } return err } func init() { provider = macOSXKeychain{} } go-keyring-0.2.4/keyring_fallback.go000066400000000000000000000010551457513566400174270ustar00rootroot00000000000000package keyring import ( "errors" "runtime" ) // All of the following methods error out on unsupported platforms var ErrUnsupportedPlatform = errors.New("unsupported platform: " + runtime.GOOS) type fallbackServiceProvider struct{} func (fallbackServiceProvider) Set(service, user, pass string) error { return ErrUnsupportedPlatform } func (fallbackServiceProvider) Get(service, user string) (string, error) { return "", ErrUnsupportedPlatform } func (fallbackServiceProvider) Delete(service, user string) error { return ErrUnsupportedPlatform } go-keyring-0.2.4/keyring_mock.go000066400000000000000000000027411457513566400166240ustar00rootroot00000000000000package keyring type mockProvider struct { mockStore map[string]map[string]string mockError error } // Set stores user and pass in the keyring under the defined service // name. func (m *mockProvider) Set(service, user, pass string) error { if m.mockError != nil { return m.mockError } if m.mockStore == nil { m.mockStore = make(map[string]map[string]string) } if m.mockStore[service] == nil { m.mockStore[service] = make(map[string]string) } m.mockStore[service][user] = pass return nil } // Get gets a secret from the keyring given a service name and a user. func (m *mockProvider) Get(service, user string) (string, error) { if m.mockError != nil { return "", m.mockError } if b, ok := m.mockStore[service]; ok { if v, ok := b[user]; ok { return v, nil } } return "", ErrNotFound } // Delete deletes a secret, identified by service & user, from the keyring. func (m *mockProvider) Delete(service, user string) error { if m.mockError != nil { return m.mockError } if m.mockStore != nil { if _, ok := m.mockStore[service]; ok { if _, ok := m.mockStore[service][user]; ok { delete(m.mockStore[service], user) return nil } } } return ErrNotFound } // MockInit sets the provider to a mocked memory store func MockInit() { provider = &mockProvider{} } // MockInitWithError sets the provider to a mocked memory store // that returns the given error on all operations func MockInitWithError(err error) { provider = &mockProvider{mockError: err} } go-keyring-0.2.4/keyring_mock_test.go000066400000000000000000000035131457513566400176610ustar00rootroot00000000000000package keyring import ( "errors" "testing" ) // TestSet tests setting a user and password in the keyring. func TestMockSet(t *testing.T) { mp := mockProvider{} err := mp.Set(service, user, password) if err != nil { t.Errorf("Should not fail, got: %s", err) } } // TestGet tests getting a password from the keyring. func TestMockGet(t *testing.T) { mp := mockProvider{} err := mp.Set(service, user, password) if err != nil { t.Errorf("Should not fail, got: %s", err) } pw, err := mp.Get(service, user) if err != nil { t.Errorf("Should not fail, got: %s", err) } if password != pw { t.Errorf("Expected password %s, got %s", password, pw) } } // TestGetNonExisting tests getting a secret not in the keyring. func TestMockGetNonExisting(t *testing.T) { mp := mockProvider{} _, err := mp.Get(service, user+"fake") assertError(t, err, ErrNotFound) } // TestDelete tests deleting a secret from the keyring. func TestMockDelete(t *testing.T) { mp := mockProvider{} err := mp.Set(service, user, password) if err != nil { t.Errorf("Should not fail, got: %s", err) } err = mp.Delete(service, user) if err != nil { t.Errorf("Should not fail, got: %s", err) } } // TestDeleteNonExisting tests deleting a secret not in the keyring. func TestMockDeleteNonExisting(t *testing.T) { mp := mockProvider{} err := mp.Delete(service, user+"fake") assertError(t, err, ErrNotFound) } func TestMockWithError(t *testing.T) { mp := mockProvider{mockError: errors.New("mock error")} err := mp.Set(service, user, password) assertError(t, err, mp.mockError) _, err = mp.Get(service, user) assertError(t, err, mp.mockError) err = mp.Delete(service, user) assertError(t, err, mp.mockError) } func assertError(t *testing.T, err error, expected error) { if err != expected { t.Errorf("Expected error %s, got %s", expected, err) } } go-keyring-0.2.4/keyring_test.go000066400000000000000000000061231457513566400166500ustar00rootroot00000000000000package keyring import ( "runtime" "strings" "testing" ) const ( service = "test-service" user = "test-user" password = "test-password" ) // TestSet tests setting a user and password in the keyring. func TestSet(t *testing.T) { err := Set(service, user, password) if err != nil { t.Errorf("Should not fail, got: %s", err) } } func TestSetTooLong(t *testing.T) { extraLongPassword := "ba" + strings.Repeat("na", 5000) err := Set(service, user, extraLongPassword) if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { // should fail on those platforms if err != ErrSetDataTooBig { t.Errorf("Should have failed, got: %s", err) } } } // TestGetMultiline tests getting a multi-line password from the keyring func TestGetMultiLine(t *testing.T) { multilinePassword := `this password has multiple lines and will be encoded by some keyring implementiations like osx` err := Set(service, user, multilinePassword) if err != nil { t.Errorf("Should not fail, got: %s", err) } pw, err := Get(service, user) if err != nil { t.Errorf("Should not fail, got: %s", err) } if multilinePassword != pw { t.Errorf("Expected password %s, got %s", multilinePassword, pw) } } // TestGetMultiline tests getting a multi-line password from the keyring func TestGetUmlaut(t *testing.T) { umlautPassword := "at least on OSX üöäÜÖÄß will be encoded" err := Set(service, user, umlautPassword) if err != nil { t.Errorf("Should not fail, got: %s", err) } pw, err := Get(service, user) if err != nil { t.Errorf("Should not fail, got: %s", err) } if umlautPassword != pw { t.Errorf("Expected password %s, got %s", umlautPassword, pw) } } // TestGetSingleLineHex tests getting a single line hex string password from the keyring. func TestGetSingleLineHex(t *testing.T) { hexPassword := "abcdef123abcdef123" err := Set(service, user, hexPassword) if err != nil { t.Errorf("Should not fail, got: %s", err) } pw, err := Get(service, user) if err != nil { t.Errorf("Should not fail, got: %s", err) } if hexPassword != pw { t.Errorf("Expected password %s, got %s", hexPassword, pw) } } // TestGet tests getting a password from the keyring. func TestGet(t *testing.T) { err := Set(service, user, password) if err != nil { t.Errorf("Should not fail, got: %s", err) } pw, err := Get(service, user) if err != nil { t.Errorf("Should not fail, got: %s", err) } if password != pw { t.Errorf("Expected password %s, got %s", password, pw) } } // TestGetNonExisting tests getting a secret not in the keyring. func TestGetNonExisting(t *testing.T) { _, err := Get(service, user+"fake") if err != ErrNotFound { t.Errorf("Expected error ErrNotFound, got %s", err) } } // TestDelete tests deleting a secret from the keyring. func TestDelete(t *testing.T) { err := Delete(service, user) if err != nil { t.Errorf("Should not fail, got: %s", err) } } // TestDeleteNonExisting tests deleting a secret not in the keyring. func TestDeleteNonExisting(t *testing.T) { err := Delete(service, user+"fake") if err != ErrNotFound { t.Errorf("Expected error ErrNotFound, got %s", err) } } go-keyring-0.2.4/keyring_unix.go000066400000000000000000000045651457513566400166640ustar00rootroot00000000000000//go:build (dragonfly && cgo) || (freebsd && cgo) || linux || netbsd || openbsd package keyring import ( "fmt" dbus "github.com/godbus/dbus/v5" ss "github.com/zalando/go-keyring/secret_service" ) type secretServiceProvider struct{} // Set stores user and pass in the keyring under the defined service // name. func (s secretServiceProvider) Set(service, user, pass string) error { svc, err := ss.NewSecretService() if err != nil { return err } // open a session session, err := svc.OpenSession() if err != nil { return err } defer svc.Close(session) attributes := map[string]string{ "username": user, "service": service, } secret := ss.NewSecret(session.Path(), pass) collection := svc.GetLoginCollection() err = svc.Unlock(collection.Path()) if err != nil { return err } err = svc.CreateItem(collection, fmt.Sprintf("Password for '%s' on '%s'", user, service), attributes, secret) if err != nil { return err } return nil } // findItem looksup an item by service and user. func (s secretServiceProvider) findItem(svc *ss.SecretService, service, user string) (dbus.ObjectPath, error) { collection := svc.GetLoginCollection() search := map[string]string{ "username": user, "service": service, } err := svc.Unlock(collection.Path()) if err != nil { return "", err } results, err := svc.SearchItems(collection, search) if err != nil { return "", err } if len(results) == 0 { return "", ErrNotFound } return results[0], nil } // Get gets a secret from the keyring given a service name and a user. func (s secretServiceProvider) Get(service, user string) (string, error) { svc, err := ss.NewSecretService() if err != nil { return "", err } item, err := s.findItem(svc, service, user) if err != nil { return "", err } // open a session session, err := svc.OpenSession() if err != nil { return "", err } defer svc.Close(session) secret, err := svc.GetSecret(item, session.Path()) if err != nil { return "", err } return string(secret.Value), nil } // Delete deletes a secret, identified by service & user, from the keyring. func (s secretServiceProvider) Delete(service, user string) error { svc, err := ss.NewSecretService() if err != nil { return err } item, err := s.findItem(svc, service, user) if err != nil { return err } return svc.Delete(item) } func init() { provider = secretServiceProvider{} } go-keyring-0.2.4/keyring_windows.go000066400000000000000000000033561457513566400173700ustar00rootroot00000000000000package keyring import ( "syscall" "github.com/danieljoos/wincred" ) type windowsKeychain struct{} // Get gets a secret from the keyring given a service name and a user. func (k windowsKeychain) Get(service, username string) (string, error) { cred, err := wincred.GetGenericCredential(k.credName(service, username)) if err != nil { if err == syscall.ERROR_NOT_FOUND { return "", ErrNotFound } return "", err } return string(cred.CredentialBlob), nil } // Set stores stores user and pass in the keyring under the defined service // name. func (k windowsKeychain) Set(service, username, password string) error { // password may not exceed 2560 bytes (https://github.com/jaraco/keyring/issues/540#issuecomment-968329967) if len(password) > 2560 { return ErrSetDataTooBig } // service may not exceed 512 bytes (might need more testing) if len(service) >= 512 { return ErrSetDataTooBig } // service may not exceed 32k but problems occur before that // so we limit it to 30k if len(service) > 1024*30 { return ErrSetDataTooBig } cred := wincred.NewGenericCredential(k.credName(service, username)) cred.UserName = username cred.CredentialBlob = []byte(password) return cred.Write() } // Delete deletes a secret, identified by service & user, from the keyring. func (k windowsKeychain) Delete(service, username string) error { cred, err := wincred.GetGenericCredential(k.credName(service, username)) if err != nil { if err == syscall.ERROR_NOT_FOUND { return ErrNotFound } return err } return cred.Delete() } // credName combines service and username to a single string. func (k windowsKeychain) credName(service, username string) string { return service + ":" + username } func init() { provider = windowsKeychain{} } go-keyring-0.2.4/secret_service/000077500000000000000000000000001457513566400166155ustar00rootroot00000000000000go-keyring-0.2.4/secret_service/secret_service.go000066400000000000000000000160671457513566400221630ustar00rootroot00000000000000package ss import ( "fmt" "errors" dbus "github.com/godbus/dbus/v5" ) const ( serviceName = "org.freedesktop.secrets" servicePath = "/org/freedesktop/secrets" serviceInterface = "org.freedesktop.Secret.Service" collectionInterface = "org.freedesktop.Secret.Collection" collectionsInterface = "org.freedesktop.Secret.Service.Collections" itemInterface = "org.freedesktop.Secret.Item" sessionInterface = "org.freedesktop.Secret.Session" promptInterface = "org.freedesktop.Secret.Prompt" loginCollectionAlias = "/org/freedesktop/secrets/aliases/default" collectionBasePath = "/org/freedesktop/secrets/collection/" ) // Secret defines a org.freedesk.Secret.Item secret struct. type Secret struct { Session dbus.ObjectPath Parameters []byte Value []byte ContentType string `dbus:"content_type"` } // NewSecret initializes a new Secret. func NewSecret(session dbus.ObjectPath, secret string) Secret { return Secret{ Session: session, Parameters: []byte{}, Value: []byte(secret), ContentType: "text/plain; charset=utf8", } } // SecretService is an interface for the Secret Service dbus API. type SecretService struct { *dbus.Conn object dbus.BusObject } // NewSecretService inializes a new SecretService object. func NewSecretService() (*SecretService, error) { conn, err := dbus.SessionBus() if err != nil { return nil, err } return &SecretService{ conn, conn.Object(serviceName, servicePath), }, nil } // OpenSession opens a secret service session. func (s *SecretService) OpenSession() (dbus.BusObject, error) { var disregard dbus.Variant var sessionPath dbus.ObjectPath err := s.object.Call(serviceInterface+".OpenSession", 0, "plain", dbus.MakeVariant("")).Store(&disregard, &sessionPath) if err != nil { return nil, err } return s.Object(serviceName, sessionPath), nil } // CheckCollectionPath accepts dbus path and returns nil if the path is found // in the collection interface (and can be used). func (s *SecretService) CheckCollectionPath(path dbus.ObjectPath) error { obj := s.Conn.Object(serviceName, servicePath) val, err := obj.GetProperty(collectionsInterface) if err != nil { return err } paths := val.Value().([]dbus.ObjectPath) for _, p := range paths { if p == path { return nil } } return errors.New("path not found") } // GetCollection returns a collection from a name. func (s *SecretService) GetCollection(name string) dbus.BusObject { return s.Object(serviceName, dbus.ObjectPath(collectionBasePath+name)) } // GetLoginCollection decides and returns the dbus collection to be used for login. func (s *SecretService) GetLoginCollection() dbus.BusObject { path := dbus.ObjectPath(collectionBasePath + "login") if err := s.CheckCollectionPath(path); err != nil { path = dbus.ObjectPath(loginCollectionAlias) } return s.Object(serviceName, path) } // Unlock unlocks a collection. func (s *SecretService) Unlock(collection dbus.ObjectPath) error { var unlocked []dbus.ObjectPath var prompt dbus.ObjectPath err := s.object.Call(serviceInterface+".Unlock", 0, []dbus.ObjectPath{collection}).Store(&unlocked, &prompt) if err != nil { return err } _, v, err := s.handlePrompt(prompt) if err != nil { return err } collections := v.Value() switch c := collections.(type) { case []dbus.ObjectPath: unlocked = append(unlocked, c...) } if len(unlocked) != 1 || (collection != loginCollectionAlias && unlocked[0] != collection) { return fmt.Errorf("failed to unlock correct collection '%v'", collection) } return nil } // Close closes a secret service dbus session. func (s *SecretService) Close(session dbus.BusObject) error { return session.Call(sessionInterface+".Close", 0).Err } // CreateCollection with the supplied label. func (s *SecretService) CreateCollection(label string) (dbus.BusObject, error) { properties := map[string]dbus.Variant{ collectionInterface + ".Label": dbus.MakeVariant(label), } var collection, prompt dbus.ObjectPath err := s.object.Call(serviceInterface+".CreateCollection", 0, properties, ""). Store(&collection, &prompt) if err != nil { return nil, err } _, v, err := s.handlePrompt(prompt) if err != nil { return nil, err } if v.String() != "" { collection = dbus.ObjectPath(v.String()) } return s.Object(serviceName, collection), nil } // CreateItem creates an item in a collection, with label, attributes and a // related secret. func (s *SecretService) CreateItem(collection dbus.BusObject, label string, attributes map[string]string, secret Secret) error { properties := map[string]dbus.Variant{ itemInterface + ".Label": dbus.MakeVariant(label), itemInterface + ".Attributes": dbus.MakeVariant(attributes), } var item, prompt dbus.ObjectPath err := collection.Call(collectionInterface+".CreateItem", 0, properties, secret, true).Store(&item, &prompt) if err != nil { return err } _, _, err = s.handlePrompt(prompt) if err != nil { return err } return nil } // handlePrompt checks if a prompt should be handles and handles it by // triggering the prompt and waiting for the Sercret service daemon to display // the prompt to the user. func (s *SecretService) handlePrompt(prompt dbus.ObjectPath) (bool, dbus.Variant, error) { if prompt != dbus.ObjectPath("/") { err := s.AddMatchSignal(dbus.WithMatchObjectPath(prompt), dbus.WithMatchInterface(promptInterface), ) if err != nil { return false, dbus.MakeVariant(""), err } defer func(s *SecretService, options ...dbus.MatchOption) { _ = s.RemoveMatchSignal(options...) }(s, dbus.WithMatchObjectPath(prompt), dbus.WithMatchInterface(promptInterface)) promptSignal := make(chan *dbus.Signal, 1) s.Signal(promptSignal) err = s.Object(serviceName, prompt).Call(promptInterface+".Prompt", 0, "").Err if err != nil { return false, dbus.MakeVariant(""), err } signal := <-promptSignal switch signal.Name { case promptInterface + ".Completed": dismissed := signal.Body[0].(bool) result := signal.Body[1].(dbus.Variant) return dismissed, result, nil } } return false, dbus.MakeVariant(""), nil } // SearchItems returns a list of items matching the search object. func (s *SecretService) SearchItems(collection dbus.BusObject, search interface{}) ([]dbus.ObjectPath, error) { var results []dbus.ObjectPath err := collection.Call(collectionInterface+".SearchItems", 0, search).Store(&results) if err != nil { return nil, err } return results, nil } // GetSecret gets secret from an item in a given session. func (s *SecretService) GetSecret(itemPath dbus.ObjectPath, session dbus.ObjectPath) (*Secret, error) { var secret Secret err := s.Object(serviceName, itemPath).Call(itemInterface+".GetSecret", 0, session).Store(&secret) if err != nil { return nil, err } return &secret, nil } // Delete deletes an item from the collection. func (s *SecretService) Delete(itemPath dbus.ObjectPath) error { var prompt dbus.ObjectPath err := s.Object(serviceName, itemPath).Call(itemInterface+".Delete", 0).Store(&prompt) if err != nil { return err } _, _, err = s.handlePrompt(prompt) if err != nil { return err } return nil }