├── .github └── workflows │ └── test.yml ├── LICENSE ├── README.md ├── doc.go ├── gnome_keyring_linux.go ├── go.mod ├── go.sum ├── keyring-example └── example.go ├── keyring.go ├── keyring_darwin.go ├── keyring_linux.go ├── keyring_test.go └── keyring_windows.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Go Test 2 | 3 | on: 4 | push: {} 5 | pull_request: {} 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macOS-latest] 13 | 14 | steps: 15 | - name: Set up Go 16 | uses: actions/setup-go@v4 17 | with: 18 | go-version: stable 19 | 20 | - name: Check out code 21 | uses: actions/checkout@v2 22 | 23 | - name: Get dependencies 24 | run: go mod download 25 | 26 | - name: Test Build 27 | run: go build -v ./... 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Travis Cline 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # keyring provides cross-platform keychain access 2 | 3 | http://godoc.org/github.com/tmc/keyring 4 | 5 | Keyring provides a common interface to keyring/keychain tools. 6 | 7 | License: ISC 8 | 9 | Currently implemented: 10 | - OSX 11 | - SecretService 12 | - gnome-keychain (via "gnome_keyring" build flag) 13 | - Windows 14 | 15 | Contributions welcome! 16 | 17 | Usage example: 18 | 19 | ```go 20 | err := keyring.Set("libraryFoo", "jack", "sacrifice") 21 | password, err := keyring.Get("libraryFoo", "jack") 22 | fmt.Println(password) //Output: sacrifice 23 | ``` 24 | 25 | ## Linux 26 | 27 | Linux requirements: 28 | 29 | ### SecretService provider 30 | 31 | - dbus 32 | 33 | ### gnome-keychain provider 34 | 35 | - gnome-keychain headers 36 | - Ubuntu/Debian: `libsecret-dev` 37 | - Fedora: `libsecret-devel` 38 | - Archlinux: `libsecret` 39 | 40 | Tests on Linux: 41 | ```sh 42 | $ go test github.com/tmc/keyring 43 | $ # for gnome-keyring provider 44 | $ go test -tags gnome_keyring github.com/tmc/keyring 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package keyring provides a cross-platform interface to keychains for 3 | password management 4 | 5 | Currently implemented: 6 | 7 | * OSX 8 | * SecretService 9 | * gnome-keychain (via "gnome_keyring" build flag) 10 | 11 | 12 | Usage 13 | 14 | Example usage: 15 | 16 | 17 | err := keyring.Set("libraryFoo", "jack", "sacrifice") 18 | password, err := keyring.Get("libraryFoo", "jack") 19 | fmt.Println(password) 20 | Output: sacrifice 21 | 22 | 23 | TODO 24 | 25 | * Write Windows provider 26 | */ 27 | package keyring 28 | -------------------------------------------------------------------------------- /gnome_keyring_linux.go: -------------------------------------------------------------------------------- 1 | //go:build gnome_keyring 2 | // +build gnome_keyring 3 | 4 | package keyring 5 | 6 | /* 7 | #cgo pkg-config: libsecret-1 glib-2.0 8 | #include 9 | #include "libsecret/secret.h" 10 | 11 | SecretSchema keyring_schema = 12 | { 13 | "org.github.tmc.keyring.Password", 14 | SECRET_SCHEMA_NONE, 15 | { 16 | { "username", SECRET_SCHEMA_ATTRIBUTE_STRING }, 17 | { "service", SECRET_SCHEMA_ATTRIBUTE_STRING }, 18 | { NULL, 0 }, 19 | } 20 | }; 21 | 22 | // wrap the gnome calls because cgo can't deal with vararg functions 23 | 24 | gboolean gkr_set_password(gchar *description, gchar *service, gchar *username, gchar *password, GError **err) { 25 | return secret_password_store_sync( 26 | &keyring_schema, 27 | NULL, 28 | description, 29 | password, 30 | NULL, 31 | err, 32 | "service", service, 33 | "username", username, 34 | NULL); 35 | } 36 | 37 | gchar * gkr_get_password(gchar *service, gchar *username, GError **err) { 38 | return secret_password_lookup_sync( 39 | &keyring_schema, 40 | NULL, 41 | err, 42 | "service", service, 43 | "username", username, 44 | NULL); 45 | } 46 | */ 47 | import "C" 48 | 49 | import ( 50 | "fmt" 51 | "unsafe" 52 | ) 53 | 54 | type gnomeKeyring struct{} 55 | 56 | func (p gnomeKeyring) Set(Service, Username, Password string) error { 57 | desc := (*C.gchar)(C.CString("Username and password for " + Service)) 58 | username := (*C.gchar)(C.CString(Username)) 59 | service := (*C.gchar)(C.CString(Service)) 60 | password := (*C.gchar)(C.CString(Password)) 61 | defer C.free(unsafe.Pointer(desc)) 62 | defer C.free(unsafe.Pointer(username)) 63 | defer C.free(unsafe.Pointer(service)) 64 | defer C.free(unsafe.Pointer(password)) 65 | 66 | var gerr *C.GError 67 | result := C.gkr_set_password(desc, service, username, password, &gerr) 68 | defer C.free(unsafe.Pointer(gerr)) 69 | 70 | if result == 0 { 71 | return fmt.Errorf("Gnome-keyring error: %+v", gerr) 72 | } 73 | return nil 74 | } 75 | 76 | func (p gnomeKeyring) Get(Service string, Username string) (string, error) { 77 | var gerr *C.GError 78 | var pw *C.gchar 79 | 80 | username := (*C.gchar)(C.CString(Username)) 81 | service := (*C.gchar)(C.CString(Service)) 82 | defer C.free(unsafe.Pointer(username)) 83 | defer C.free(unsafe.Pointer(service)) 84 | 85 | pw = C.gkr_get_password(service, username, &gerr) 86 | defer C.free(unsafe.Pointer(gerr)) 87 | defer C.secret_password_free((*C.gchar)(pw)) 88 | 89 | if pw == nil { 90 | return "", fmt.Errorf("Gnome-keyring error: %+v", gerr) 91 | } 92 | return C.GoString((*C.char)(pw)), nil 93 | } 94 | 95 | func initializeProvider() (provider, error) { 96 | return gnomeKeyring{}, nil 97 | } 98 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tmc/keyring 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/danieljoos/wincred v1.1.2 7 | github.com/godbus/dbus/v5 v5.1.0 8 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 9 | ) 10 | 11 | require ( 12 | golang.org/x/sys v0.1.0 // indirect 13 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= 2 | github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= 6 | github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 10 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 11 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 12 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 13 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= 14 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 15 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 16 | golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 18 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 19 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= 20 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 22 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 23 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 24 | -------------------------------------------------------------------------------- /keyring-example/example.go: -------------------------------------------------------------------------------- 1 | // Shows example use of the keyring package 2 | // 3 | // May need to be built with a platform-specific build flag to specify a 4 | // provider. See keyring documentation for details. 5 | // 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "syscall" 12 | 13 | "github.com/tmc/keyring" 14 | "golang.org/x/crypto/ssh/terminal" 15 | ) 16 | 17 | func main() { 18 | if pw, err := keyring.Get("keyring_example", "jack"); err == nil { 19 | fmt.Println("current stored password:", pw) 20 | } else if err == keyring.ErrNotFound { 21 | fmt.Println("no password stored yet") 22 | } else { 23 | fmt.Println("got unexpected error:", err) 24 | os.Exit(1) 25 | } 26 | 27 | fmt.Printf("enter new password: ") 28 | pw, err := terminal.ReadPassword(int(syscall.Stdin)) 29 | if err != nil { 30 | fmt.Println(err) 31 | os.Exit(1) 32 | } 33 | fmt.Println("setting keyring_example/jack to..", pw) 34 | err = keyring.Set("keyring_example", "jack", string(pw)) 35 | if err != nil { 36 | fmt.Println(err) 37 | os.Exit(1) 38 | } 39 | fmt.Println("fetching keyring_example/jack..") 40 | if pw, err := keyring.Get("keyring_example", "jack"); err == nil { 41 | fmt.Println("got", pw) 42 | } else { 43 | fmt.Println("error:", err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /keyring.go: -------------------------------------------------------------------------------- 1 | package keyring 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | // ErrNotFound means the requested password was not found 10 | ErrNotFound = errors.New("keyring: Password not found") 11 | // ErrNoDefault means that no default keyring provider has been found 12 | ErrNoDefault = errors.New("keyring: No suitable keyring provider found (check your build flags)") 13 | 14 | providerInitOnce sync.Once 15 | defaultProvider provider 16 | providerInitError error 17 | ) 18 | 19 | // provider provides a simple interface to keychain sevice 20 | type provider interface { 21 | Get(service, username string) (string, error) 22 | Set(service, username, password string) error 23 | } 24 | 25 | func setupProvider() (provider, error) { 26 | providerInitOnce.Do(func() { 27 | defaultProvider, providerInitError = initializeProvider() 28 | }) 29 | 30 | if providerInitError != nil { 31 | return nil, providerInitError 32 | } else if defaultProvider == nil { 33 | return nil, ErrNoDefault 34 | } 35 | return defaultProvider, nil 36 | } 37 | 38 | // Get gets the password for a paricular Service and Username using the 39 | // default keyring provider. 40 | func Get(service, username string) (string, error) { 41 | p, err := setupProvider() 42 | if err != nil { 43 | return "", err 44 | } 45 | 46 | return p.Get(service, username) 47 | } 48 | 49 | // Set sets the password for a particular Service and Username using the 50 | // default keyring provider. 51 | func Set(service, username, password string) error { 52 | p, err := setupProvider() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | return p.Set(service, username, password) 58 | } 59 | -------------------------------------------------------------------------------- /keyring_darwin.go: -------------------------------------------------------------------------------- 1 | package keyring 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "regexp" 7 | "strconv" 8 | "syscall" 9 | ) 10 | 11 | type osxProvider struct { 12 | } 13 | 14 | var pwRe = regexp.MustCompile(`password:\s+(?:0x[A-Fa-f0-9]+\s+)?"(.+)"`) 15 | 16 | var escapeCodeRegexp = regexp.MustCompile(`\\([0-3][0-7]{2})`) 17 | 18 | func unescapeOne(code []byte) []byte { 19 | i, _ := strconv.ParseUint(string(code[1:]), 8, 8) 20 | return []byte{byte(i)} 21 | } 22 | 23 | func unescape(raw string) string { 24 | if !escapeCodeRegexp.MatchString(raw) { 25 | return raw 26 | } else { 27 | return string(escapeCodeRegexp.ReplaceAllFunc([]byte(raw), unescapeOne)) 28 | } 29 | } 30 | 31 | func (p osxProvider) Get(Service, Username string) (string, error) { 32 | args := []string{"find-generic-password", 33 | "-s", Service, 34 | "-a", Username, 35 | "-g"} 36 | c := exec.Command("/usr/bin/security", args...) 37 | o, err := c.CombinedOutput() 38 | if err != nil { 39 | exitCode := c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() 40 | // check particular exit code 41 | if exitCode == 44 { 42 | return "", ErrNotFound 43 | } 44 | return "", fmt.Errorf("/usr/bin/security: %s", err) 45 | } 46 | matches := pwRe.FindStringSubmatch(string(o)) 47 | if len(matches) != 2 { 48 | return "", ErrNotFound 49 | } 50 | return unescape(matches[1]), nil 51 | } 52 | 53 | func (p osxProvider) Set(Service, Username, Password string) error { 54 | args := []string{"add-generic-password", 55 | "-s", Service, 56 | "-a", Username, 57 | "-w", Password, 58 | "-U"} 59 | c := exec.Command("/usr/bin/security", args...) 60 | err := c.Run() 61 | if err != nil { 62 | o, _ := c.CombinedOutput() 63 | return fmt.Errorf(string(o)) 64 | } 65 | return nil 66 | } 67 | 68 | func initializeProvider() (provider, error) { 69 | return osxProvider{}, nil 70 | } 71 | -------------------------------------------------------------------------------- /keyring_linux.go: -------------------------------------------------------------------------------- 1 | //go:build !gnome_keyring 2 | // +build !gnome_keyring 3 | 4 | package keyring 5 | 6 | import ( 7 | "fmt" 8 | 9 | dbus "github.com/godbus/dbus/v5" 10 | ) 11 | 12 | const ( 13 | ssServiceName = "org.freedesktop.secrets" 14 | ssServicePath = "/org/freedesktop/secrets" 15 | ssCollectionPath = "/org/freedesktop/secrets/aliases/default" 16 | ssServiceIface = "org.freedesktop.Secret.Service." 17 | ssSessionIface = "org.freedesktop.Secret.Session." 18 | ssCollectionIface = "org.freedesktop.Secret.Collection." 19 | ssItemIface = "org.freedesktop.Secret.Item." 20 | ssPromptIface = "org.freedesktop.Secret.Prompt." 21 | ) 22 | 23 | // ssSecret corresponds to org.freedesktop.Secret.Item 24 | // Note: Order is important 25 | type ssSecret struct { 26 | Session dbus.ObjectPath 27 | Parameters []byte 28 | Value []byte 29 | ContentType string `dbus:"content_type"` 30 | } 31 | 32 | // newSSSecret prepares an ssSecret for use 33 | // Uses text/plain as the Content-type which may need to change in the future 34 | func newSSSecret(session dbus.ObjectPath, secret string) (s ssSecret) { 35 | s = ssSecret{ 36 | ContentType: "text/plain; charset=utf8", 37 | Parameters: []byte{}, 38 | Session: session, 39 | Value: []byte(secret), 40 | } 41 | return 42 | } 43 | 44 | // ssProvider implements the provider interface freedesktop SecretService 45 | type ssProvider struct { 46 | *dbus.Conn 47 | srv dbus.BusObject 48 | } 49 | 50 | // This is used to open a seassion for every get/set. Alternative might be to 51 | // defer() the call to close when constructing the ssProvider 52 | func (s *ssProvider) openSession() (dbus.BusObject, error) { 53 | var disregard dbus.Variant 54 | var sessionPath dbus.ObjectPath 55 | method := fmt.Sprint(ssServiceIface, "OpenSession") 56 | err := s.srv.Call(method, 0, "plain", dbus.MakeVariant("")).Store(&disregard, &sessionPath) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return s.Object(ssServiceName, sessionPath), nil 61 | } 62 | 63 | // Unsure how the .Prompt call surfaces, it hasn't come up. 64 | func (s *ssProvider) unlock(p dbus.ObjectPath) error { 65 | var unlocked []dbus.ObjectPath 66 | var prompt dbus.ObjectPath 67 | method := fmt.Sprint(ssServiceIface, "Unlock") 68 | err := s.srv.Call(method, 0, []dbus.ObjectPath{p}).Store(&unlocked, &prompt) 69 | if err != nil { 70 | return fmt.Errorf("keyring/dbus: Unlock error: %s", err) 71 | } 72 | if prompt != dbus.ObjectPath("/") { 73 | method = fmt.Sprint(ssPromptIface, "Prompt") 74 | call := s.Object(ssServiceName, prompt).Call(method, 0, "unlock") 75 | return call.Err 76 | } 77 | return nil 78 | } 79 | 80 | func (s *ssProvider) Get(c, u string) (string, error) { 81 | results := []dbus.ObjectPath{} 82 | var secret ssSecret 83 | search := map[string]string{ 84 | "username": u, 85 | "service": c, 86 | } 87 | 88 | session, err := s.openSession() 89 | if err != nil { 90 | return "", err 91 | } 92 | defer session.Call(fmt.Sprint(ssSessionIface, "Close"), 0) 93 | s.unlock(ssCollectionPath) 94 | collection := s.Object(ssServiceName, ssCollectionPath) 95 | 96 | method := fmt.Sprint(ssCollectionIface, "SearchItems") 97 | call := collection.Call(method, 0, search) 98 | err = call.Store(&results) 99 | if call.Err != nil { 100 | return "", call.Err 101 | } 102 | // results is a slice. Just grab the first one. 103 | if len(results) == 0 { 104 | return "", ErrNotFound 105 | } 106 | 107 | method = fmt.Sprint(ssItemIface, "GetSecret") 108 | err = s.Object(ssServiceName, results[0]).Call(method, 0, session.Path()).Store(&secret) 109 | if err != nil { 110 | return "", err 111 | } 112 | return string(secret.Value), nil 113 | } 114 | 115 | func (s *ssProvider) Set(c, u, p string) error { 116 | var item, prompt dbus.ObjectPath 117 | properties := map[string]dbus.Variant{ 118 | "org.freedesktop.Secret.Item.Label": dbus.MakeVariant(fmt.Sprintf("%s - %s", u, c)), 119 | "org.freedesktop.Secret.Item.Attributes": dbus.MakeVariant(map[string]string{ 120 | "username": u, 121 | "service": c, 122 | }), 123 | } 124 | 125 | session, err := s.openSession() 126 | if err != nil { 127 | return err 128 | } 129 | defer session.Call(fmt.Sprint(ssSessionIface, "Close"), 0) 130 | s.unlock(ssCollectionPath) 131 | collection := s.Object(ssServiceName, ssCollectionPath) 132 | 133 | secret := newSSSecret(session.Path(), p) 134 | // the bool is "replace" 135 | err = collection.Call(fmt.Sprint(ssCollectionIface, "CreateItem"), 0, properties, secret, true).Store(&item, &prompt) 136 | if err != nil { 137 | return fmt.Errorf("keyring/dbus: CreateItem error: %s", err) 138 | } 139 | if prompt != "/" { 140 | s.Object(ssServiceName, prompt).Call(fmt.Sprint(ssPromptIface, "Prompt"), 0, "unlock") 141 | } 142 | return nil 143 | } 144 | 145 | func initializeProvider() (provider, error) { 146 | conn, err := dbus.SessionBus() 147 | if err != nil { 148 | return nil, err 149 | } 150 | srv := conn.Object(ssServiceName, ssServicePath) 151 | p := &ssProvider{conn, srv} 152 | return p, nil 153 | } 154 | -------------------------------------------------------------------------------- /keyring_test.go: -------------------------------------------------------------------------------- 1 | package keyring 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func assertPasswordSticks(t *testing.T, user, password string) { 8 | var ( 9 | pw string 10 | err error 11 | ) 12 | pw, err = Get("keyring-test", user) 13 | if err != nil { 14 | // ok on initial invokation 15 | t.Logf("(expected) Initial Get() error for %s: %s", user, err) 16 | } 17 | err = Set("keyring-test", user, password) 18 | if err != nil { 19 | t.Errorf("Set() error for %s: %s", user, err) 20 | } 21 | pw, err = Get("keyring-test", user) 22 | if err != nil { 23 | t.Errorf("Get() error for %s: %s", user, err) 24 | } 25 | 26 | if pw != password { 27 | t.Errorf("expected '%s' for %s, got '%s'", password, user, pw) 28 | } 29 | } 30 | 31 | func TestBasicSetGet(t *testing.T) { 32 | cases := []struct { 33 | user string 34 | password string 35 | }{ 36 | {"jack", "foo"}, 37 | {"jill", "bar"}, 38 | {"alice", "cr4zyp!s\\%"}, 39 | {"punctuator", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"}, 40 | {"pierre", "bérets"}, 41 | {"unibomba", "I❤Unicode"}, 42 | } 43 | for _, testCase := range cases { 44 | assertPasswordSticks(t, testCase.user, testCase.password) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /keyring_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package keyring 4 | 5 | import ( 6 | "github.com/danieljoos/wincred" 7 | ) 8 | 9 | type winProvider struct { 10 | } 11 | 12 | func (p *winProvider) Get(Service, Username string) (string, error) { 13 | cred1, err := wincred.GetGenericCredential(Service) 14 | if err == nil && cred1.UserName == Username { 15 | return string(cred1.CredentialBlob), nil 16 | } 17 | 18 | return "", ErrNotFound 19 | } 20 | 21 | func (p *winProvider) Set(Service, Username, Password string) error { 22 | cred := wincred.NewGenericCredential(Service) 23 | cred.UserName = Username 24 | cred.CredentialBlob = []byte(Password) 25 | return cred.Write() 26 | } 27 | 28 | func initializeProvider() (provider, error) { 29 | return &winProvider{}, nil 30 | } 31 | --------------------------------------------------------------------------------