github.com/versent/saml2aws@v2.17.0+incompatible/helper/osxkeychain/osxkeychain_darwin.go (about) 1 // Copyright (c) 2016 David Calavera 2 3 // Permission is hereby granted, free of charge, to any person obtaining 4 // a copy of this software and associated documentation files (the 5 // "Software"), to deal in the Software without restriction, including 6 // without limitation the rights to use, copy, modify, merge, publish, 7 // distribute, sublicense, and/or sell copies of the Software, and to 8 // permit persons to whom the Software is furnished to do so, subject to 9 // the following conditions: 10 11 // The above copyright notice and this permission notice shall be 12 // included in all copies or substantial portions of the Software. 13 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 // 22 // https://github.com/docker/docker-credential-helpers 23 package osxkeychain 24 25 /* 26 #cgo CFLAGS: -x objective-c -mmacosx-version-min=10.10 27 #cgo LDFLAGS: -framework Security -framework Foundation 28 29 #include "osxkeychain_darwin.h" 30 #include <stdlib.h> 31 */ 32 import "C" 33 import ( 34 "errors" 35 "net/url" 36 "strconv" 37 "strings" 38 "unsafe" 39 40 "github.com/sirupsen/logrus" 41 "github.com/versent/saml2aws/helper/credentials" 42 ) 43 44 var logger = logrus.WithField("helper", "osxkeychain") 45 46 // errCredentialsNotFound is the specific error message returned by OS X 47 // when the credentials are not in the keychain. 48 const errCredentialsNotFound = "The specified item could not be found in the keychain." 49 50 // Osxkeychain handles secrets using the OS X Keychain as store. 51 type Osxkeychain struct{} 52 53 // Add adds new credentials to the keychain. 54 func (h Osxkeychain) Add(creds *credentials.Credentials) error { 55 h.Delete(creds.ServerURL) 56 57 s, err := splitServer(creds.ServerURL) 58 if err != nil { 59 return err 60 } 61 defer freeServer(s) 62 63 label := C.CString(credentials.CredsLabel) 64 defer C.free(unsafe.Pointer(label)) 65 username := C.CString(creds.Username) 66 defer C.free(unsafe.Pointer(username)) 67 secret := C.CString(creds.Secret) 68 defer C.free(unsafe.Pointer(secret)) 69 70 errMsg := C.keychain_add(s, label, username, secret) 71 if errMsg != nil { 72 defer C.free(unsafe.Pointer(errMsg)) 73 return errors.New(C.GoString(errMsg)) 74 } 75 76 return nil 77 } 78 79 // Delete removes credentials from the keychain. 80 func (h Osxkeychain) Delete(serverURL string) error { 81 s, err := splitServer(serverURL) 82 if err != nil { 83 return err 84 } 85 defer freeServer(s) 86 87 errMsg := C.keychain_delete(s) 88 if errMsg != nil { 89 defer C.free(unsafe.Pointer(errMsg)) 90 return errors.New(C.GoString(errMsg)) 91 } 92 93 return nil 94 } 95 96 // Get returns the username and secret to use for a given registry server URL. 97 func (h Osxkeychain) Get(serverURL string) (string, string, error) { 98 99 logger.WithField("serverURL", serverURL).Debug("Get credentials") 100 101 s, err := splitServer(serverURL) 102 if err != nil { 103 return "", "", err 104 } 105 defer freeServer(s) 106 107 var usernameLen C.uint 108 var username *C.char 109 var secretLen C.uint 110 var secret *C.char 111 defer C.free(unsafe.Pointer(username)) 112 defer C.free(unsafe.Pointer(secret)) 113 114 errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret) 115 if errMsg != nil { 116 defer C.free(unsafe.Pointer(errMsg)) 117 goMsg := C.GoString(errMsg) 118 119 if goMsg == errCredentialsNotFound { 120 logger.WithField("goMsg", goMsg).Debug("Get credentials") 121 return "", "", credentials.ErrCredentialsNotFound 122 } 123 124 logger.WithField("goMsg", goMsg).Error("keychain Get returned error") 125 return "", "", errors.New(goMsg) 126 } 127 128 user := C.GoStringN(username, C.int(usernameLen)) 129 pass := C.GoStringN(secret, C.int(secretLen)) 130 131 logger.WithField("user", user).Debug("Get credentials") 132 133 return user, pass, nil 134 } 135 136 // List returns the stored URLs and corresponding usernames. 137 func (h Osxkeychain) List() (map[string]string, error) { 138 credsLabelC := C.CString(credentials.CredsLabel) 139 defer C.free(unsafe.Pointer(credsLabelC)) 140 141 var pathsC **C.char 142 defer C.free(unsafe.Pointer(pathsC)) 143 var acctsC **C.char 144 defer C.free(unsafe.Pointer(acctsC)) 145 var listLenC C.uint 146 errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC) 147 if errMsg != nil { 148 defer C.free(unsafe.Pointer(errMsg)) 149 goMsg := C.GoString(errMsg) 150 return nil, errors.New(goMsg) 151 } 152 153 defer C.freeListData(&pathsC, listLenC) 154 defer C.freeListData(&acctsC, listLenC) 155 156 var listLen int 157 listLen = int(listLenC) 158 pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen] 159 acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen] 160 //taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper 161 resp := make(map[string]string) 162 for i := 0; i < listLen; i++ { 163 if C.GoString(pathTmp[i]) == "0" { 164 continue 165 } 166 resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i]) 167 } 168 return resp, nil 169 } 170 171 // SupportsCredentialsStorage returns true since storage is supported 172 func (Osxkeychain) SupportsCredentialStorage() bool { 173 return true 174 } 175 176 func splitServer(serverURL string) (*C.struct_Server, error) { 177 u, err := url.Parse(serverURL) 178 if err != nil { 179 return nil, err 180 } 181 182 hostAndPort := strings.Split(u.Host, ":") 183 host := hostAndPort[0] 184 var port int 185 if len(hostAndPort) == 2 { 186 p, err := strconv.Atoi(hostAndPort[1]) 187 if err != nil { 188 return nil, err 189 } 190 port = p 191 } 192 193 proto := C.kSecProtocolTypeHTTPS 194 if u.Scheme != "https" { 195 proto = C.kSecProtocolTypeHTTP 196 } 197 198 return &C.struct_Server{ 199 proto: C.SecProtocolType(proto), 200 host: C.CString(host), 201 port: C.uint(port), 202 path: C.CString(u.Path), 203 }, nil 204 } 205 206 func freeServer(s *C.struct_Server) { 207 C.free(unsafe.Pointer(s.host)) 208 C.free(unsafe.Pointer(s.path)) 209 }